Docker Deployment

This guide covers deploying the Rust OAuth2 Server using Docker and Docker Compose.

Prebuilt image (Docker Hub)

If you want to run the server without compiling, use the prebuilt Docker Hub image.

See: Docker Hub Image

Quick Start

The fastest way to get started with Docker:

# Clone the repository
git clone https://github.com/ianlintner/rust_oauth2_server.git
cd rust_oauth2_server

# Start with Docker Compose
docker-compose up -d

The server will be available at http://localhost:8080.

Docker Architecture

graph TB subgraph DockerComposeStack[Docker Compose Stack] subgraph OAuth2Service[OAuth2 Service] OAuth2[OAuth2 Server Container] end subgraph DatabaseService[Database Service] DB[(PostgreSQL Container)] end subgraph Observability Jaeger[Jaeger Container] Prometheus[Prometheus Container] end subgraph Networks AppNet[app-network] end end OAuth2 --> DB OAuth2 --> Jaeger Prometheus --> OAuth2 AppNet -.-> OAuth2 AppNet -.-> DB AppNet -.-> Jaeger AppNet -.-> Prometheus style OAuth2 fill:#ff9800,color:#fff style DB fill:#f3e5f5 style Jaeger fill:#fff9c4 style Prometheus fill:#e8f5e9

Building the Docker Image

Using the Provided Dockerfile

# Build the image
docker build -t rust_oauth2_server:latest .

# (Optional) Build with MongoDB backend support
# docker build --build-arg CARGO_FEATURES=mongo -t rust_oauth2_server:latest .

# Run the container
docker run -d \
  -p 8080:8080 \
  -e OAUTH2_DATABASE_URL=sqlite:oauth2.db?mode=rwc \
  -e OAUTH2_JWT_SECRET=your-secret-key \
  --name oauth2_server \
  rust_oauth2_server:latest

# (Optional) Run against MongoDB (requires image built with --build-arg CARGO_FEATURES=mongo)
# docker run -d \
#   -p 8080:8080 \
#   -e OAUTH2_DATABASE_URL=mongodb://mongo:27017/oauth2 \
#   -e OAUTH2_JWT_SECRET=your-secret-key \
#   --name oauth2_server \
#   rust_oauth2_server:latest

Multi-Stage Dockerfile

The project includes an optimized multi-stage Dockerfile:

# Stage 1: Build
FROM rust:1.75-slim as builder

WORKDIR /app

# Copy manifests
COPY Cargo.toml Cargo.lock ./

# Copy source code
COPY src ./src
COPY migrations ./migrations
COPY templates ./templates
COPY static ./static

# Build release
RUN cargo build --release

# Stage 2: Runtime
FROM debian:bookworm-slim

# Install runtime dependencies
RUN apt-get update && apt-get install -y \
    ca-certificates \
    libssl3 \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

# Copy binary from builder
COPY --from=builder /app/target/release/rust_oauth2_server .

# Copy runtime assets
COPY templates ./templates
COPY static ./static
COPY migrations ./migrations

# Expose port
EXPOSE 8080

# Run the server
CMD ["./rust_oauth2_server"]

Docker Compose Setup

Basic Configuration

Create docker-compose.yml:

version: "3.8"

services:
  oauth2_server:
    build: .
    ports:
      - "8080:8080"
    environment:
      OAUTH2_SERVER_HOST: 0.0.0.0
      OAUTH2_SERVER_PORT: 8080
      OAUTH2_DATABASE_URL: postgresql://oauth2:password@postgres:5432/oauth2_db
      OAUTH2_JWT_SECRET: ${JWT_SECRET:-change-this-in-production}
      OAUTH2_SESSION_KEY: ${SESSION_KEY:-change-this-in-production-must-be-64-chars}
      RUST_LOG: info
    depends_on:
      postgres:
        condition: service_healthy
    networks:
      - app-network
    restart: unless-stopped

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: oauth2_db
      POSTGRES_USER: oauth2
      POSTGRES_PASSWORD: password
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - app-network
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U oauth2"]
      interval: 5s
      timeout: 5s
      retries: 5
    restart: unless-stopped

networks:
  app-network:
    driver: bridge

volumes:
  postgres_data:

With Observability Stack

Complete setup with monitoring:

version: "3.8"

services:
  oauth2_server:
    build: .
    ports:
      - "8080:8080"
    environment:
      OAUTH2_SERVER_HOST: 0.0.0.0
      OAUTH2_SERVER_PORT: 8080
      OAUTH2_DATABASE_URL: postgresql://oauth2:password@postgres:5432/oauth2_db
      OAUTH2_JWT_SECRET: ${JWT_SECRET}
      OAUTH2_SESSION_KEY: ${SESSION_KEY}
      OAUTH2_OTLP_ENDPOINT: http://jaeger:4317
      RUST_LOG: info
    depends_on:
      - postgres
      - jaeger
    networks:
      - app-network
    restart: unless-stopped

  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: oauth2_db
      POSTGRES_USER: oauth2
      POSTGRES_PASSWORD: password
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - app-network
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U oauth2"]
      interval: 5s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  jaeger:
    image: jaegertracing/all-in-one:latest
    ports:
      - "16686:16686" # Jaeger UI
      - "4317:4317" # OTLP gRPC
    environment:
      COLLECTOR_OTLP_ENABLED: "true"
    networks:
      - app-network
    restart: unless-stopped

  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    command:
      - "--config.file=/etc/prometheus/prometheus.yml"
      - "--storage.tsdb.path=/prometheus"
    networks:
      - app-network
    restart: unless-stopped

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    environment:
      GF_SECURITY_ADMIN_PASSWORD: admin
    volumes:
      - grafana_data:/var/lib/grafana
    networks:
      - app-network
    restart: unless-stopped

networks:
  app-network:
    driver: bridge

volumes:
  postgres_data:
  prometheus_data:
  grafana_data:

Prometheus Configuration

Create prometheus.yml:

global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: "oauth2_server"
    static_configs:
      - targets: ["oauth2_server:8080"]
    metrics_path: "/metrics"

Environment Variables

Create .env file for Docker Compose:

# Server Configuration
OAUTH2_SERVER_HOST=0.0.0.0
OAUTH2_SERVER_PORT=8080

# Database
OAUTH2_DATABASE_URL=postgresql://oauth2:password@postgres:5432/oauth2_db

# Security (IMPORTANT: Change these!)
JWT_SECRET=your-secure-random-secret-minimum-32-characters
SESSION_KEY=your-secure-session-key-must-be-at-least-64-characters

# Token Expiration
OAUTH2_ACCESS_TOKEN_EXPIRATION=3600
OAUTH2_REFRESH_TOKEN_EXPIRATION=2592000

# Observability
OAUTH2_OTLP_ENDPOINT=http://jaeger:4317
RUST_LOG=info

# Social Login (Optional)
# OAUTH2_GOOGLE_CLIENT_ID=
# OAUTH2_GOOGLE_CLIENT_SECRET=

Database Migrations

Automatic Migrations

Run migrations automatically on startup:

services:
  oauth2_server:
    # ...
    entrypoint: ["/bin/sh", "-c"]
    command:
      - |
        # Run migrations
        flyway migrate
        # Start server
        ./rust_oauth2_server

Using Init Container

services:
  migration:
    image: flyway/flyway:10-alpine
    command: migrate
    volumes:
      - ./migrations/sql:/flyway/sql
      - ./flyway.conf:/flyway/conf/flyway.conf
    networks:
      - app-network
    depends_on:
      postgres:
        condition: service_healthy

  oauth2_server:
    # ...
    depends_on:
      - migration
      - postgres

Docker Commands

Build

# Build image
docker-compose build

# Build without cache
docker-compose build --no-cache

# Build specific service
docker-compose build oauth2_server

Run

# Start all services
docker-compose up -d

# Start specific service
docker-compose up -d oauth2_server

# View logs
docker-compose logs -f oauth2_server

# View all logs
docker-compose logs -f

Stop

# Stop all services
docker-compose down

# Stop and remove volumes
docker-compose down -v

# Stop specific service
docker-compose stop oauth2_server

Restart

# Restart all services
docker-compose restart

# Restart specific service
docker-compose restart oauth2_server

Status

# View running containers
docker-compose ps

# View resource usage
docker-compose stats

Health Checks

Add health checks to your services:

services:
  oauth2_server:
    # ...
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

Volume Management

Backup Database

# Backup PostgreSQL
docker-compose exec postgres pg_dump -U oauth2 oauth2_db > backup.sql

# Restore from backup
docker-compose exec -T postgres psql -U oauth2 oauth2_db < backup.sql

Persistent Data

Volumes ensure data persists across container restarts:

volumes:
  postgres_data:
    driver: local

  # Or use named volume with specific driver
  postgres_data:
    driver: local
    driver_opts:
      type: none
      device: /path/to/data
      o: bind

Networking

Expose Ports

services:
  oauth2_server:
    ports:
      - "8080:8080" # host:container
      - "127.0.0.1:8080:8080" # bind to localhost only

Custom Network

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge

services:
  oauth2_server:
    networks:
      - frontend
      - backend

  postgres:
    networks:
      - backend

Security

Run as Non-Root User

Update Dockerfile:

# Create non-root user
RUN useradd -m -u 1000 oauth2user

# Change ownership
RUN chown -R oauth2user:oauth2user /app

# Switch to non-root user
USER oauth2user

CMD ["./rust_oauth2_server"]

Secrets Management

Use Docker secrets:

version: "3.8"

services:
  oauth2_server:
    secrets:
      - jwt_secret
      - session_key
    environment:
      OAUTH2_JWT_SECRET_FILE: /run/secrets/jwt_secret
      OAUTH2_SESSION_KEY_FILE: /run/secrets/session_key

secrets:
  jwt_secret:
    file: ./secrets/jwt_secret.txt
  session_key:
    file: ./secrets/session_key.txt

Production Deployment

Use HTTPS

Behind a reverse proxy (Nginx/Traefik):

services:
  nginx:
    image: nginx:alpine
    ports:
      - "443:443"
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
      - ./certs:/etc/nginx/certs
    depends_on:
      - oauth2_server
    networks:
      - app-network

  oauth2_server:
    # No ports exposed directly
    expose:
      - "8080"
    networks:
      - app-network

Resource Limits

services:
  oauth2_server:
    deploy:
      resources:
        limits:
          cpus: "2"
          memory: 2G
        reservations:
          cpus: "0.5"
          memory: 512M

Restart Policy

services:
  oauth2_server:
    restart: unless-stopped
    # or
    restart: always
    # or
    restart: on-failure:3

Monitoring

Docker Stats

# View resource usage
docker stats oauth2_server

# Continuous monitoring
watch docker stats

Container Logs

# Follow logs
docker-compose logs -f oauth2_server

# Last 100 lines
docker-compose logs --tail=100 oauth2_server

# Logs since 1 hour ago
docker-compose logs --since 1h oauth2_server

Troubleshooting

Container Won't Start

# Check logs
docker-compose logs oauth2_server

# Inspect container
docker inspect oauth2_server

# Check configuration
docker-compose config

Database Connection Issues

# Check if database is running
docker-compose ps postgres

# Test connection
docker-compose exec oauth2_server nc -zv postgres 5432

# View database logs
docker-compose logs postgres

Permission Issues

# Check file ownership
docker-compose exec oauth2_server ls -la

# Fix permissions
sudo chown -R 1000:1000 ./data

Next Steps