Docker Deployment

Deploying GoPie with Docker and Docker Compose

This guide covers deploying GoPie using Docker and Docker Compose, including development, staging, and production configurations.

Prerequisites

  • Docker Engine 20.10+
  • Docker Compose 2.0+
  • 8GB RAM minimum (16GB recommended)
  • 20GB free disk space

Quick Start

Development Deployment

# Clone the repository
git clone https://github.com/your-org/gopie.git
cd gopie

# Copy environment files
cp .env.example .env

# Start all services
docker-compose up -d

# Check service health
docker-compose ps
docker-compose logs -f

# Access the application
# Web UI: http://localhost:3000
# API: http://localhost:8000
# Chat Server: http://localhost:8001

Development Without Auth

# Use no-auth configuration for faster development
docker-compose -f docker-compose-noauth.yaml up -d

Docker Images

Web Frontend Dockerfile

# web/Dockerfile
FROM node:20-alpine AS base

# Install dependencies only when needed
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app

# Install dependencies
COPY package.json package-lock.json* ./
RUN npm ci

# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Build the application
ENV NEXT_TELEMETRY_DISABLED 1
RUN npm run build

# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app

ENV NODE_ENV production
ENV NEXT_TELEMETRY_DISABLED 1

# Create non-root user
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

# Copy built application
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

# Copy WASM files
COPY --from=builder /app/public/wasm ./public/wasm

USER nextjs

EXPOSE 3000

ENV PORT 3000
ENV HOSTNAME "0.0.0.0"

CMD ["node", "server.js"]

Go Backend Dockerfile

# server/Dockerfile
# Build stage
FROM golang:1.21-alpine AS builder

RUN apk add --no-cache git gcc musl-dev sqlite-dev

WORKDIR /app

# Copy go mod files
COPY go.mod go.sum ./
RUN go mod download

# Copy source code
COPY . .

# Build the application
RUN CGO_ENABLED=1 GOOS=linux go build -a -installsuffix cgo -o gopie main.go

# Final stage
FROM alpine:latest

RUN apk --no-cache add ca-certificates tzdata

WORKDIR /root/

# Copy the binary
COPY --from=builder /app/gopie .

# Copy migration files
COPY --from=builder /app/infrastructure/postgres/migrations ./migrations

# Install goose for migrations
RUN apk add --no-cache curl
RUN curl -fsSL \
    https://raw.githubusercontent.com/pressly/goose/master/install.sh \
    | sh

EXPOSE 8000

CMD ["./gopie", "serve"]

Python Chat Server Dockerfile

# chat-server/Dockerfile
FROM python:3.11-slim

# Install system dependencies
RUN apt-get update && apt-get install -y \
    gcc \
    g++ \
    curl \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

# Install Python dependencies
COPY pyproject.toml .
RUN pip install --no-cache-dir uv && \
    uv pip install --system -e .

# Copy application code
COPY . .

# Create non-root user
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
USER appuser

EXPOSE 8001

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8001"]

Docker Compose Configuration

Full Stack Development

# docker-compose.yml
version: '3.8'

services:
  # PostgreSQL Database
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: gopie
      POSTGRES_PASSWORD: gopie
      POSTGRES_DB: gopie
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./scripts/init-db.sql:/docker-entrypoint-initdb.d/init.sql
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U gopie"]
      interval: 10s
      timeout: 5s
      retries: 5

  # MinIO S3-compatible storage
  minio:
    image: minio/minio:latest
    command: server /data --console-address ":9001"
    environment:
      MINIO_ROOT_USER: minioadmin
      MINIO_ROOT_PASSWORD: minioadmin
      MINIO_DEFAULT_BUCKETS: gopie-datasets:public,gopie-exports:private
    volumes:
      - minio_data:/data
    ports:
      - "9000:9000"
      - "9001:9001"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 30s
      timeout: 20s
      retries: 3

  # Qdrant Vector Database
  qdrant:
    image: qdrant/qdrant:latest
    volumes:
      - qdrant_data:/qdrant/storage
    ports:
      - "6333:6333"
      - "6334:6334"
    environment:
      QDRANT__LOG_LEVEL: INFO

  # Zitadel Authentication
  zitadel:
    image: ghcr.io/zitadel/zitadel:latest
    command: 'start-from-init --masterkey "MasterkeyNeedsToHave32Characters" --tlsMode disabled'
    environment:
      ZITADEL_DATABASE_POSTGRES_HOST: postgres
      ZITADEL_DATABASE_POSTGRES_PORT: 5432
      ZITADEL_DATABASE_POSTGRES_DATABASE: zitadel
      ZITADEL_DATABASE_POSTGRES_USER_USERNAME: zitadel
      ZITADEL_DATABASE_POSTGRES_USER_PASSWORD: zitadel
      ZITADEL_DATABASE_POSTGRES_USER_SSL_MODE: disable
      ZITADEL_EXTERNALSECURE: false
      ZITADEL_EXTERNALPORT: 4455
      ZITADEL_EXTERNALHOST: localhost
    depends_on:
      postgres:
        condition: service_healthy
    ports:
      - "4455:8080"

  # Go Backend Server
  gopie-server:
    build:
      context: ./server
      dockerfile: Dockerfile
    environment:
      DATABASE_URL: postgres://gopie:gopie@postgres:5432/gopie?sslmode=disable
      S3_ENDPOINT: http://minio:9000
      S3_ACCESS_KEY: minioadmin
      S3_SECRET_KEY: minioadmin
      S3_BUCKET_NAME: gopie-datasets
      S3_REGION: us-east-1
      S3_USE_SSL: "false"
      ZITADEL_DOMAIN: http://zitadel:8080
      CORS_ALLOWED_ORIGINS: http://localhost:3000
    volumes:
      - ./server:/app
    ports:
      - "8000:8000"
    depends_on:
      postgres:
        condition: service_healthy
      minio:
        condition: service_healthy
    command: >
      sh -c "
        goose -dir ./migrations postgres \"$$DATABASE_URL\" up &&
        ./gopie serve
      "

  # Python Chat Server
  gopie-chat:
    build:
      context: ./chat-server
      dockerfile: Dockerfile
    environment:
      BACKEND_URL: http://gopie-server:8000
      QDRANT_URL: http://qdrant:6333
      OPENAI_API_KEY: ${OPENAI_API_KEY}
      ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY}
      LOG_LEVEL: INFO
    volumes:
      - ./chat-server:/app
    ports:
      - "8001:8001"
    depends_on:
      - gopie-server
      - qdrant

  # Web Frontend
  gopie-web:
    build:
      context: ./web
      dockerfile: Dockerfile
      args:
        NEXT_PUBLIC_BACKEND_URL: http://localhost:8000
        NEXT_PUBLIC_CHAT_SERVER_URL: http://localhost:8001
        NEXT_PUBLIC_APP_URL: http://localhost:3000
    environment:
      NEXT_PUBLIC_BACKEND_URL: http://localhost:8000
      NEXT_PUBLIC_CHAT_SERVER_URL: http://localhost:8001
      NEXT_PUBLIC_APP_URL: http://localhost:3000
      NEXT_PUBLIC_ZITADEL_ISSUER: http://localhost:4455
    ports:
      - "3000:3000"
    depends_on:
      - gopie-server
      - gopie-chat

volumes:
  postgres_data:
  minio_data:
  qdrant_data:

Production Configuration

# docker-compose.prod.yml
version: '3.8'

services:
  gopie-server:
    image: gopie/server:latest
    environment:
      ENVIRONMENT: production
      LOG_LEVEL: WARN
      DATABASE_URL: ${DATABASE_URL}
      S3_ENDPOINT: ${S3_ENDPOINT}
      S3_ACCESS_KEY: ${S3_ACCESS_KEY}
      S3_SECRET_KEY: ${S3_SECRET_KEY}
    deploy:
      replicas: 3
      resources:
        limits:
          cpus: '2'
          memory: 2G
        reservations:
          cpus: '1'
          memory: 1G
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3

  gopie-chat:
    image: gopie/chat-server:latest
    environment:
      ENVIRONMENT: production
      LOG_LEVEL: WARN
      BACKEND_URL: http://gopie-server:8000
      QDRANT_URL: ${QDRANT_URL}
    deploy:
      replicas: 2
      resources:
        limits:
          cpus: '4'
          memory: 4G
        reservations:
          cpus: '2'
          memory: 2G

  gopie-web:
    image: gopie/web:latest
    environment:
      NODE_ENV: production
    deploy:
      replicas: 2
      resources:
        limits:
          cpus: '1'
          memory: 1G
        reservations:
          cpus: '0.5'
          memory: 512M

  nginx:
    image: nginx:alpine
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - ./nginx/ssl:/etc/nginx/ssl
    ports:
      - "80:80"
      - "443:443"
    depends_on:
      - gopie-web
      - gopie-server
      - gopie-chat

Network Configuration

Custom Networks

# docker-compose.networks.yml
version: '3.8'

networks:
  frontend:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/24
  
  backend:
    driver: bridge
    ipam:
      config:
        - subnet: 172.21.0.0/24
  
  data:
    driver: bridge
    ipam:
      config:
        - subnet: 172.22.0.0/24

services:
  gopie-web:
    networks:
      - frontend
  
  gopie-server:
    networks:
      - frontend
      - backend
      - data
  
  gopie-chat:
    networks:
      - backend
      - data
  
  postgres:
    networks:
      - data
  
  qdrant:
    networks:
      - data
  
  minio:
    networks:
      - data

Volume Management

Named Volumes

volumes:
  postgres_data:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: /data/gopie/postgres
  
  minio_data:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: /data/gopie/minio
  
  qdrant_data:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: /data/gopie/qdrant

Backup Volumes

#!/bin/bash
# backup-volumes.sh

BACKUP_DIR="/backups/gopie/$(date +%Y%m%d_%H%M%S)"
mkdir -p "$BACKUP_DIR"

# Backup PostgreSQL
docker run --rm \
  -v gopie_postgres_data:/data \
  -v "$BACKUP_DIR":/backup \
  alpine tar czf /backup/postgres_data.tar.gz -C /data .

# Backup MinIO
docker run --rm \
  -v gopie_minio_data:/data \
  -v "$BACKUP_DIR":/backup \
  alpine tar czf /backup/minio_data.tar.gz -C /data .

# Backup Qdrant
docker run --rm \
  -v gopie_qdrant_data:/data \
  -v "$BACKUP_DIR":/backup \
  alpine tar czf /backup/qdrant_data.tar.gz -C /data .

echo "Backup completed: $BACKUP_DIR"

Health Checks

Service Health Monitoring

# docker-compose.health.yml
services:
  gopie-server:
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
  
  gopie-chat:
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8001/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
  
  gopie-web:
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 60s

Health Check Script

#!/bin/bash
# check-health.sh

services=("gopie-server:8000" "gopie-chat:8001" "gopie-web:3000" "postgres:5432" "minio:9000" "qdrant:6333")

for service in "${services[@]}"; do
    name="${service%%:*}"
    port="${service##*:}"
    
    if curl -f "http://localhost:$port/health" &>/dev/null; then
        echo "$name is healthy"
    else
        echo "$name is unhealthy"
    fi
done

Security Hardening

Docker Security

# Secure Dockerfile practices
FROM node:20-alpine AS base

# Run as non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001

# Set secure permissions
RUN chmod -R 755 /app
RUN chown -R nextjs:nodejs /app

# Drop capabilities
RUN apk add --no-cache libcap
RUN setcap -r /usr/local/bin/node

USER nextjs

Secrets Management

# docker-compose.secrets.yml
version: '3.8'

secrets:
  db_password:
    file: ./secrets/db_password.txt
  openai_api_key:
    file: ./secrets/openai_api_key.txt
  jwt_secret:
    file: ./secrets/jwt_secret.txt

services:
  gopie-server:
    secrets:
      - db_password
      - jwt_secret
    environment:
      DATABASE_PASSWORD_FILE: /run/secrets/db_password
      JWT_SECRET_FILE: /run/secrets/jwt_secret
  
  gopie-chat:
    secrets:
      - openai_api_key
    environment:
      OPENAI_API_KEY_FILE: /run/secrets/openai_api_key

Performance Optimization

Build Optimization

# Multi-stage build with cache mounts
FROM golang:1.21-alpine AS builder

# Cache Go modules
RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    go mod download

# Build with cache
RUN --mount=type=cache,target=/go/pkg/mod \
    --mount=type=cache,target=/root/.cache/go-build \
    go build -o gopie .

Resource Limits

# docker-compose.limits.yml
services:
  gopie-server:
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 2G
        reservations:
          cpus: '0.5'
          memory: 512M
    ulimits:
      nofile:
        soft: 65536
        hard: 65536

Monitoring Integration

Prometheus & Grafana

# docker-compose.monitoring.yml
services:
  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
    ports:
      - "9090:9090"
  
  grafana:
    image: grafana/grafana:latest
    volumes:
      - grafana_data:/var/lib/grafana
      - ./grafana/dashboards:/etc/grafana/provisioning/dashboards
      - ./grafana/datasources:/etc/grafana/provisioning/datasources
    environment:
      GF_SECURITY_ADMIN_PASSWORD: admin
      GF_USERS_ALLOW_SIGN_UP: false
    ports:
      - "3001:3000"
  
  node-exporter:
    image: prom/node-exporter:latest
    ports:
      - "9100:9100"
  
  cadvisor:
    image: gcr.io/cadvisor/cadvisor:latest
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:ro
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro
    ports:
      - "8080:8080"

volumes:
  prometheus_data:
  grafana_data:

Deployment Scripts

Deploy Script

#!/bin/bash
# deploy.sh

set -e

ENVIRONMENT="${1:-development}"

echo "Deploying GoPie in $ENVIRONMENT mode..."

# Load environment variables
if [ -f ".env.$ENVIRONMENT" ]; then
    export $(cat ".env.$ENVIRONMENT" | xargs)
fi

# Pull latest images
docker-compose pull

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

# Run database migrations
docker-compose run --rm gopie-server goose -dir ./migrations postgres "$DATABASE_URL" up

# Start services
if [ "$ENVIRONMENT" = "production" ]; then
    docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
else
    docker-compose up -d
fi

# Wait for services to be healthy
echo "Waiting for services to be healthy..."
sleep 30

# Check health
./check-health.sh

echo "Deployment complete!"

Rollback Script

#!/bin/bash
# rollback.sh

set -e

BACKUP_TAG="${1:-latest-backup}"

echo "Rolling back to $BACKUP_TAG..."

# Stop current services
docker-compose down

# Restore from backup tags
docker-compose up -d \
    --scale gopie-server=0 \
    --scale gopie-chat=0 \
    --scale gopie-web=0

# Start services with backup images
docker run -d --name gopie-server-backup gopie/server:$BACKUP_TAG
docker run -d --name gopie-chat-backup gopie/chat-server:$BACKUP_TAG
docker run -d --name gopie-web-backup gopie/web:$BACKUP_TAG

echo "Rollback complete!"

Troubleshooting

Common Issues

  1. Container fails to start:

    # Check logs
    docker-compose logs gopie-server
    
    # Check container details
    docker inspect gopie-server
  2. Database connection issues:

    # Test database connection
    docker-compose exec postgres psql -U gopie -d gopie -c "SELECT 1"
    
    # Check network connectivity
    docker-compose exec gopie-server ping postgres
  3. Permission issues:

    # Fix volume permissions
    docker-compose exec gopie-server chown -R 1001:1001 /data

Debug Mode

# docker-compose.debug.yml
services:
  gopie-server:
    command: ["dlv", "debug", "--headless", "--listen=:2345", "--api-version=2"]
    ports:
      - "2345:2345"
    environment:
      CGO_ENABLED: 0

Next Steps