Skip to main content

Overview

WhatDoc does not currently include official Docker support out of the box, but you can easily containerize the application yourself. This guide provides production-ready Dockerfiles and docker-compose configuration for self-hosting.

Prerequisites

  • Docker 20.10+
  • Docker Compose 2.0+
  • MongoDB (local or cloud instance)
  • At least 2GB RAM
  • Valid Gemini API key(s)

Quick Start with Docker Compose

Create the following files in your project root:

1. Server Dockerfile

Create server/Dockerfile:
FROM node:18-alpine

WORKDIR /app

# Install git for simple-git dependency
RUN apk add --no-cache git

# Copy package files
COPY package*.json ./

# Install dependencies
RUN npm ci --only=production

# Copy application code
COPY . .

# Expose port
EXPOSE 3000

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

2. Client Dockerfile

Create client/Dockerfile:
FROM node:18-alpine AS builder

WORKDIR /app

# Copy package files
COPY package*.json ./

# Install dependencies
RUN npm ci

# Copy source code
COPY . .

# Build application
RUN npm run build

# Production stage
FROM nginx:alpine

# Copy built assets
COPY --from=builder /app/dist /usr/share/nginx/html

# Copy nginx configuration
COPY nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

3. Nginx Configuration

Create client/nginx.conf:
server {
    listen 80;
    server_name localhost;
    root /usr/share/nginx/html;
    index index.html;

    # SPA routing
    location / {
        try_files $uri $uri/ /index.html;
    }

    # API proxy (optional, if not using CORS)
    location /api {
        proxy_pass http://server:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }

    # Gzip compression
    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
}

4. Docker Compose Configuration

Create docker-compose.yml in the project root:
version: '3.8'

services:
  mongodb:
    image: mongo:7
    container_name: whatdoc-mongodb
    restart: unless-stopped
    ports:
      - "27017:27017"
    volumes:
      - mongodb_data:/data/db
    environment:
      MONGO_INITDB_DATABASE: whatdoc
    networks:
      - whatdoc-network

  redis:
    image: redis:7-alpine
    container_name: whatdoc-redis
    restart: unless-stopped
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    networks:
      - whatdoc-network

  server:
    build:
      context: ./server
      dockerfile: Dockerfile
    container_name: whatdoc-server
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - MONGO_URI=mongodb://mongodb:27017/whatdoc
      - REDIS_URL=redis://redis:6379
      - JWT_SECRET=${JWT_SECRET}
      - GEMINI_API_KEYS=${GEMINI_API_KEYS}
      - PORT=3000
      - CLIENT_URL=${CLIENT_URL}
      - CORS_ORIGINS=${CORS_ORIGINS}
      - APP_DOMAIN=${APP_DOMAIN}
      - GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID}
      - GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET}
      - GITHUB_CALLBACK_URL=${GITHUB_CALLBACK_URL}
    depends_on:
      - mongodb
      - redis
    networks:
      - whatdoc-network
    volumes:
      - /tmp:/tmp  # For ephemeral git clones

  client:
    build:
      context: ./client
      dockerfile: Dockerfile
      args:
        - VITE_API_URL=${VITE_API_URL}
        - VITE_APP_DOMAIN=${VITE_APP_DOMAIN}
    container_name: whatdoc-client
    restart: unless-stopped
    ports:
      - "80:80"
    depends_on:
      - server
    networks:
      - whatdoc-network

volumes:
  mongodb_data:
  redis_data:

networks:
  whatdoc-network:
    driver: bridge

5. Environment Configuration

Create .env in the project root:
# JWT
JWT_SECRET=your-super-secure-random-string-min-32-chars

# Gemini API (comma-separated for rotation)
GEMINI_API_KEYS=AIzaSy...,AIzaSy...,AIzaSy...

# URLs
CLIENT_URL=http://localhost
CORS_ORIGINS=http://localhost,https://yourdomain.com
APP_DOMAIN=yourdomain.com

# GitHub OAuth
GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret
GITHUB_CALLBACK_URL=http://localhost/auth/github/callback

# Client
VITE_API_URL=http://localhost:3000
VITE_APP_DOMAIN=localhost
[!WARNING] Never commit the .env file to version control. Add it to .gitignore immediately.

Deployment

1. Build and Start Services

# Build images
docker-compose build

# Start all services
docker-compose up -d

# View logs
docker-compose logs -f

2. Verify Services

# Check running containers
docker-compose ps

# Test server health
curl http://localhost:3000/health

# Access the application
open http://localhost

3. Monitor Logs

# All services
docker-compose logs -f

# Specific service
docker-compose logs -f server
docker-compose logs -f client
docker-compose logs -f mongodb

Production Considerations

Reverse Proxy Setup

For production, use a reverse proxy like Traefik or Nginx:
# Add to docker-compose.yml
services:
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx-prod.conf:/etc/nginx/nginx.conf:ro
      - ./ssl:/etc/nginx/ssl:ro
    depends_on:
      - client
      - server
    networks:
      - whatdoc-network

SSL/TLS Configuration

For HTTPS support:
  1. Obtain SSL certificates (Let’s Encrypt recommended)
  2. Mount certificates in the nginx service
  3. Update nginx-prod.conf with SSL directives
server {
    listen 443 ssl http2;
    server_name yourdomain.com;

    ssl_certificate /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    location / {
        proxy_pass http://client:80;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }

    location /api {
        proxy_pass http://server:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Resource Limits

Add resource constraints to prevent memory issues:
services:
  server:
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 2G
        reservations:
          cpus: '0.5'
          memory: 512M

Health Checks

Add health checks for automatic restarts:
services:
  server:
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

Maintenance

Update Application

# Pull latest changes
git pull origin main

# Rebuild and restart
docker-compose down
docker-compose build --no-cache
docker-compose up -d

Backup Database

# Backup MongoDB
docker exec whatdoc-mongodb mongodump --db whatdoc --archive=/data/backup.archive

# Copy backup to host
docker cp whatdoc-mongodb:/data/backup.archive ./backup-$(date +%Y%m%d).archive

Restore Database

# Copy backup to container
docker cp ./backup.archive whatdoc-mongodb:/data/backup.archive

# Restore
docker exec whatdoc-mongodb mongorestore --db whatdoc --archive=/data/backup.archive

Clean Up

# Stop and remove containers
docker-compose down

# Remove volumes (WARNING: deletes all data)
docker-compose down -v

# Remove images
docker-compose down --rmi all

Troubleshooting

Server Won’t Start

  1. Check environment variables:
    docker-compose config
    
  2. Verify MongoDB connection:
    docker exec -it whatdoc-mongodb mongosh
    
  3. Check server logs:
    docker-compose logs server
    

Client Build Fails

  1. Ensure build args are passed correctly
  2. Check Vite configuration
  3. Verify Node.js version (18+)

Git Clone Failures

The server needs git installed and write access to /tmp:
server:
  volumes:
    - /tmp:/tmp  # Required for simple-git
  cap_add:
    - SYS_ADMIN  # If using overlay filesystem
[!TIP] For large repos, increase Docker’s storage allocation in Docker Desktop settings (Disk image size).

Alternative: Single Container

For smaller deployments, combine server and client:
FROM node:18-alpine

WORKDIR /app

# Install dependencies
RUN apk add --no-cache git nginx

# Build client
WORKDIR /app/client
COPY client/package*.json ./
RUN npm ci
COPY client/ ./
RUN npm run build

# Setup server
WORKDIR /app/server
COPY server/package*.json ./
RUN npm ci --only=production
COPY server/ ./

# Configure nginx
COPY nginx.conf /etc/nginx/http.d/default.conf
RUN cp -r /app/client/dist /usr/share/nginx/html

# Start script
COPY start.sh /start.sh
RUN chmod +x /start.sh

EXPOSE 80 3000

CMD ["/start.sh"]
With start.sh:
#!/bin/sh
nginx
cd /app/server && node server.js
This approach reduces complexity but loses service isolation.

Build docs developers (and LLMs) love