Skip to content

Docker Production Deployment Guide

This guide covers deploying VUP projects to production using Docker and Docker Compose. This deployment solution is suitable for full-stack applications (API + Frontend + Database) and is optimized for applications with DAU ≤ 5000.

What is Docker Deployment

Docker deployment provides:

  • Containerization - Isolated environment for each service
  • Easy Updates - Update by replacing files and restarting, no need to rebuild images
  • Resource Management - CPU and memory limits to prevent resource exhaustion
  • Service Orchestration - Manage multiple services (database, API, frontend, nginx) with a single command
  • Volume Mapping - Direct file mapping for easy updates without rebuilding

Prerequisites

Before starting, ensure you have:

  • Docker >= 20.10
  • Docker Compose >= 2.0
  • Server with at least 2GB RAM and 2 CPU cores
  • Domain name (optional, for HTTPS)

For production deployments, it's recommended to use a standard Linux directory structure:

Recommended Path: /opt/vup or /opt/vup-cli

This follows Linux FHS (Filesystem Hierarchy Standard) conventions where /opt is used for optional application software packages.

Setup Deployment Directory

bash
# Create deployment directory
sudo mkdir -p /opt/vup
sudo chown $USER:$USER /opt/vup

# Copy deploy directory to recommended location
cp -r /path/to/project-vue/deploy/* /opt/vup/

# Or clone/copy the entire deploy directory
cd /opt/vup

Directory Structure

After setup, your deployment directory should look like:

/opt/vup/
├── docker-compose.yml
├── .env
├── builds/
├── nginx/
├── scripts/
├── data/
├── logs/
└── backups/

Note: All paths in this guide assume deployment in /opt/vup. If you use a different directory, adjust the paths accordingly.

Quick Start

1. Setup Deployment Directory

bash
# Create and navigate to deployment directory
sudo mkdir -p /opt/vup
sudo chown $USER:$USER /opt/vup
cd /opt/vup

# Copy deploy directory from your project
cp -r /path/to/project-vue/deploy/* /opt/vup/

2. Prepare Environment Variables

Create a .env file in the deployment directory:

bash
cd /opt/vup
cp .env.example .env

Edit .env with your configuration:

bash
# Database Configuration
MYSQL_DATABASE=vup_db
MYSQL_USER=vup_user
MYSQL_PASSWORD=your_secure_password
MYSQL_ROOT_PASSWORD=your_root_password
MYSQL_PORT=3306

# API Configuration
CORS_ORIGIN=https://yourdomain.com
JWT_SECRET=your_jwt_secret_key
JWT_EXPIRES_IN=7d
JWT_REFRESH_EXPIRES_IN=30d
UPLOAD_MAX_FILE_SIZE=1073741824
UPLOAD_BASE_URL=https://yourdomain.com/uploads

# Frontend Configuration
API_BASE_URL=https://yourdomain.com/api
APP_BASE_URL=https://yourdomain.com

# Port Configuration
HTTP_PORT=80
HTTPS_PORT=443

3. Build Applications

Build your API and Admin applications:

bash
# Build API
cd apps/nest-template
pnpm build

# Build Admin (Nuxt)
cd apps/nuxt-template
pnpm build

4. Copy Build Outputs

Copy build outputs to the deployment directory:

bash
# Copy API build output
cp -r /path/to/project-vue/apps/nest-template/.output /opt/vup/builds/api

# Copy Admin build output
cp -r /path/to/project-vue/apps/nuxt-template/.output /opt/vup/builds/admin

5. Start Services

bash
cd /opt/vup

# Start all services
docker compose up -d

# View logs
docker compose logs -f

# Check service status
docker compose ps

6. Run Database Migrations

bash
# Run migrations
docker compose run --rm migration

Service Overview

MariaDB Database

  • Container: vup-mariadb
  • Image: mariadb:11.3
  • Port: 3306 (configurable via MYSQL_PORT)
  • Data Volume: ./data/mariadb
  • Backup Volume: ./backups/mariadb
  • Resource Limits: 0.8 CPU, 1GB RAM

API Service

  • Container: vup-api
  • Image: node:20-alpine
  • Port: 9310 (internal)
  • Build Directory: ./builds/api
  • Resource Limits: 0.6 CPU, 512MB RAM

Admin Frontend Service

  • Container: vup-admin
  • Image: node:20-alpine
  • Port: 3000 (internal, Nuxt SSR)
  • Build Directory: ./builds/admin
  • Resource Limits: 0.2 CPU, 256MB RAM

Nginx Reverse Proxy

  • Container: vup-nginx
  • Image: nginx:alpine
  • Ports: 80 (HTTP), 443 (HTTPS)
  • Resource Limits: 0.2 CPU, 128MB RAM

Migration Service

  • Container: migration (one-time use)
  • Image: node:20-alpine
  • Profile: migration (doesn't start automatically)

Configuration Details

Docker Compose Configuration

The docker-compose.yml file uses volume mapping for easy updates:

  • Advantage: Update by replacing files and restarting, no need to rebuild images
  • Suitable for: DAU ≤ 5000
  • Update Process: Replace build files → Restart service

Nginx Configuration

Nginx acts as a reverse proxy with the following routes:

  • /api/http://api:9310/api/ - API service
  • /admin/http://admin:3000/ - Admin frontend service
  • /uploads/ → Static file service (from volume mount)
  • / → Redirects to /admin/
  • /health → Health check endpoint

Features

  1. Reverse Proxy - API and Admin services accessed through reverse proxy
  2. Static File Service - Uploaded files served directly by Nginx
  3. File Upload Support - Maximum upload size 1GB
  4. Gzip Compression - Enabled for better performance
  5. Cache Control - Upload files cached for 30 days
  6. CORS Support - Cross-origin access for uploaded files

Testing Nginx Configuration

bash
# Test configuration syntax
docker compose exec nginx nginx -t

# Reload configuration (without downtime)
docker compose exec nginx nginx -s reload

# View logs
docker compose exec nginx tail -f /var/log/nginx/access.log
docker compose exec nginx tail -f /var/log/nginx/error.log

HTTPS Configuration (Optional)

To enable HTTPS:

  1. Place SSL certificate files in nginx/ssl/:

    • cert.pem - SSL certificate
    • key.pem - SSL private key
  2. Uncomment the HTTPS server block in nginx/conf.d/default.conf

  3. Update server_name to your actual domain

  4. Restart nginx:

bash
docker compose restart nginx

Note: For production, consider using Let's Encrypt for free SSL certificates.

Deployment Scripts

The deploy/scripts/ directory contains utility scripts for maintenance:

backup.sh - Database Backup

Backs up MariaDB database and uploaded files, automatically cleans old backups (retains 7 days).

bash
./scripts/backup.sh

Cron Configuration (Daily at 2:00 AM):

bash
# Assuming deployment in /opt/vup
0 2 * * * /opt/vup/scripts/backup.sh >> /opt/vup/logs/backup.log 2>&1

cleanup.sh - Cleanup Script

Cleans old logs (retains 30 days), old backups (retains 7 days), and unused Docker resources. Checks disk usage.

bash
./scripts/cleanup.sh

Cron Configuration (Daily at 3:00 AM):

bash
# Assuming deployment in /opt/vup
0 3 * * * /opt/vup/scripts/cleanup.sh >> /opt/vup/logs/cleanup.log 2>&1

monitor.sh - Monitoring Script

Displays service status, resource usage, disk and memory usage, health checks, and alerts.

bash
./scripts/monitor.sh

Cron Configuration (Hourly):

bash
# Assuming deployment in /opt/vup
0 * * * * /opt/vup/scripts/monitor.sh >> /opt/vup/logs/monitor.log 2>&1

restore.sh - Database Restore

Restores database from backup file. Automatically creates a temporary backup before restoration.

bash
# List backups
ls -lh backups/mariadb/

# Restore specific backup
./scripts/restore.sh backups/mariadb/backup_20240101_020000.sql.gz

Warning: Restore operation will overwrite the current database. A temporary backup is created automatically before restoration.

Setting Up Cron Jobs

Create a setup script (/opt/vup/scripts/setup-cron.sh):

bash
#!/bin/bash

# Assuming deployment in /opt/vup
DEPLOY_DIR="/opt/vup"

# Backup task (Daily at 2:00 AM)
echo "0 2 * * * ${DEPLOY_DIR}/scripts/backup.sh >> ${DEPLOY_DIR}/logs/backup.log 2>&1" | crontab -

# Cleanup task (Daily at 3:00 AM)
echo "0 3 * * * ${DEPLOY_DIR}/scripts/cleanup.sh >> ${DEPLOY_DIR}/logs/cleanup.log 2>&1" | crontab -

# Monitor task (Hourly)
echo "0 * * * * ${DEPLOY_DIR}/scripts/monitor.sh >> ${DEPLOY_DIR}/logs/monitor.log 2>&1" | crontab -

echo "Cron jobs configured"

Make it executable:

bash
chmod +x /opt/vup/scripts/setup-cron.sh
/opt/vup/scripts/setup-cron.sh

Ensure scripts have execute permissions:

bash
chmod +x scripts/*.sh

Daily Operations

View Logs

bash
# View all service logs
docker compose logs -f

# View specific service logs
docker compose logs -f api
docker compose logs -f admin
docker compose logs -f nginx
docker compose logs -f mariadb

# View local log files
tail -f logs/api/app.log
tail -f logs/admin/app.log
tail -f nginx/logs/access.log
tail -f nginx/logs/error.log

Restart Services

bash
# Restart all services
docker compose restart

# Restart specific service
docker compose restart api
docker compose restart admin
docker compose restart nginx

# Stop all services
docker compose stop

# Start all services
docker compose start

Health Checks

bash
# Check service status
docker compose ps

# Check API health
curl http://localhost/api/health

# Check Admin health
curl http://localhost/admin/

# Check database connection
docker compose exec mariadb mysqladmin ping -h localhost

Update Application

  1. Build new version:
bash
# Build API
cd apps/nest-template
pnpm build

# Build Admin
cd apps/nuxt-template
pnpm build
  1. Copy build outputs:
bash
# Backup current version (optional)
cp -r /opt/vup/builds/api /opt/vup/builds/api.backup
cp -r /opt/vup/builds/admin /opt/vup/builds/admin.backup

# Copy new builds
cp -r /path/to/project-vue/apps/nest-template/.output /opt/vup/builds/api
cp -r /path/to/project-vue/apps/nuxt-template/.output /opt/vup/builds/admin
  1. Restart services:
bash
cd /opt/vup
docker compose restart api admin

Troubleshooting

502 Bad Gateway

  • Check if API or Admin services are running:
    bash
    docker compose ps
  • Check service logs:
    bash
    docker compose logs api
    docker compose logs admin
  • Verify service names in nginx config (api, admin)
  • Verify ports (API: 9310, Admin: 3000)

403 Forbidden

  • Check file permissions:
    bash
    ls -la /opt/vup/data/uploads
  • Verify uploads directory exists
  • Check nginx configuration

413 Request Entity Too Large

  • Check client_max_body_size in nginx config
  • Verify API service UPLOAD_MAX_FILE_SIZE environment variable

Database Connection Issues

  • Check database container status:
    bash
    docker compose ps mariadb
  • Check database logs:
    bash
    docker compose logs mariadb
  • Verify environment variables:
    bash
    docker compose exec api env | grep MYSQL

Service Won't Start

  • Check resource limits (CPU/memory)
  • Check disk space:
    bash
    df -h
  • Check Docker logs:
    bash
    docker compose logs

Script Execution Issues

  • Check script permissions:
    bash
    ls -l scripts/
    chmod +x scripts/*.sh
  • Test script manually:
    bash
    ./scripts/backup.sh

Best Practices

Resource Management

  • Monitor resource usage regularly:
    bash
    docker stats
  • Adjust resource limits in docker-compose.yml based on actual usage
  • Set appropriate CPU and memory limits to prevent resource exhaustion

Backup Strategy

  • Daily Backups: Configure cron job for daily database backups
  • Backup Retention: Keep backups for at least 7 days
  • Test Restores: Periodically test backup restoration
  • Offsite Backups: Store backups in a separate location

Monitoring

  • Set up hourly monitoring with monitor.sh
  • Monitor disk usage to prevent full disk
  • Set up alerts for service failures
  • Monitor resource usage trends

Security

  • Use strong passwords for database
  • Keep Docker and images updated
  • Use HTTPS in production
  • Restrict database port access (use firewall)
  • Regularly review and update environment variables

Performance Optimization

  • Enable Gzip compression in Nginx
  • Configure appropriate cache headers
  • Monitor and optimize database queries
  • Use CDN for static assets (if applicable)

Directory Structure

The recommended deployment directory structure (/opt/vup):

/opt/vup/
├── docker-compose.yml      # Docker Compose configuration
├── .env                    # Environment variables (not in git)
├── builds/                 # Build outputs
│   ├── api/               # API build output
│   └── admin/             # Admin build output
├── nginx/                  # Nginx configuration
│   ├── nginx.conf         # Main nginx config
│   ├── conf.d/            # Site configurations
│   │   └── default.conf   # Default site config
│   ├── ssl/               # SSL certificates (optional)
│   └── logs/              # Nginx logs
├── scripts/                # Deployment scripts
│   ├── backup.sh          # Database backup
│   ├── cleanup.sh         # Cleanup script
│   ├── monitor.sh         # Monitoring script
│   ├── restore.sh         # Database restore
│   └── setup-cron.sh       # Cron setup script
├── data/                   # Data volumes
│   ├── mariadb/           # Database data
│   └── uploads/            # Uploaded files
├── logs/                   # Application logs
│   ├── api/               # API logs
│   ├── admin/             # Admin logs
│   ├── backup.log         # Backup script logs
│   ├── cleanup.log        # Cleanup script logs
│   └── monitor.log        # Monitor script logs
└── backups/               # Backup files
    ├── mariadb/           # Database backups
    └── api/               # API backups

Note: If you deploy to a different directory, adjust all paths in scripts and cron jobs accordingly.