MOPC-App/DEPLOYMENT.md

7.2 KiB

MOPC Platform - Server Deployment Guide

Deployment guide for the MOPC platform on a Linux VPS with Docker.

Domain: portal.monaco-opc.com App Port: 7600 (behind Nginx reverse proxy) CI/CD: Gitea Actions (Ubuntu runner) builds and pushes Docker images

CI/CD Pipeline

The app is built automatically by a Gitea runner on every push to main:

  1. Gitea Actions workflow builds the Docker image on Ubuntu
  2. Image is pushed to the Gitea container registry
  3. On the server, docker compose up -d refreshes the image and restarts the app

Gitea Setup

Configure the following in your Gitea repository settings:

Repository Variables (Settings > Actions > Variables):

Variable Value
REGISTRY_URL Your Gitea registry URL (e.g. gitea.example.com/your-org)

Repository Secrets (Settings > Actions > Secrets):

Secret Value
REGISTRY_USER Gitea username with registry access
REGISTRY_PASSWORD Gitea access token or password

The workflow file is at .gitea/workflows/build.yml.

Prerequisites

  • Linux VPS (Ubuntu 22.04+ recommended)
  • Docker Engine 24+ with Compose v2
  • Nginx installed on the host
  • Certbot for SSL certificates

Install Docker (if needed)

curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
# Log out and back in

Install Nginx & Certbot (if needed)

sudo apt update
sudo apt install nginx certbot python3-certbot-nginx

First-Time Deployment

1. Clone the repository

git clone <your-repo-url> /opt/mopc
cd /opt/mopc

2. Configure environment variables

cp docker/.env.production docker/.env
nano docker/.env

Fill in all CHANGE_ME values. Generate secrets with:

openssl rand -base64 32

Required variables:

Variable Description
REGISTRY_URL Gitea registry URL (e.g. gitea.example.com/your-org)
DB_PASSWORD PostgreSQL password
NEXTAUTH_SECRET Auth session secret (openssl rand)
NEXTAUTH_URL https://portal.monaco-opc.com
MINIO_ENDPOINT MinIO internal URL (e.g. http://localhost:9000)
MINIO_ACCESS_KEY MinIO access key
MINIO_SECRET_KEY MinIO secret key
MINIO_BUCKET MinIO bucket name (mopc-files)
SMTP_HOST SMTP server host
SMTP_PORT SMTP port (587)
SMTP_USER SMTP username
SMTP_PASS SMTP password
EMAIL_FROM Sender address

3. Run the deploy script

chmod +x scripts/deploy.sh scripts/seed.sh scripts/update.sh
./scripts/deploy.sh

This will:

  • Log in to the container registry
  • Pull the latest app image
  • Start PostgreSQL + the app
  • Run database migrations automatically on startup
  • Wait for the health check

4. Seed the database (one time only)

./scripts/seed.sh

This seeds:

  • Super admin user (matt.ciaccio@gmail.com)
  • System settings
  • Program & Round 1 configuration
  • Evaluation form
  • All candidature data from CSV

5. Set up Nginx

sudo ln -s /opt/mopc/docker/nginx/mopc-platform.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

6. Set up SSL

sudo certbot --nginx -d portal.monaco-opc.com

Auto-renewal is configured by default. Test with:

sudo certbot renew --dry-run

7. Verify

curl https://portal.monaco-opc.com/api/health

Expected response:

{"status":"healthy","timestamp":"...","services":{"database":"connected"}}

Updating the Platform

After Gitea CI builds a new image (push to main):

cd /opt/mopc
./scripts/update.sh

This pulls the latest image from the registry, restarts only the app container (PostgreSQL stays running), runs migrations via the entrypoint, and waits for the health check.

Manual equivalent:

cd /opt/mopc/docker
docker compose up -d --pull always --force-recreate app

prisma migrate deploy runs automatically in the container entrypoint before the app starts.

Manual Operations

View logs

cd /opt/mopc/docker
docker compose logs -f app       # App logs
docker compose logs -f postgres   # Database logs

Run migrations manually

cd /opt/mopc/docker
docker compose exec app npx prisma migrate deploy

Open a shell in the app container

cd /opt/mopc/docker
docker compose exec app sh

Restart services

cd /opt/mopc/docker
docker compose restart app       # App only
docker compose restart           # All services

Stop everything

cd /opt/mopc/docker
docker compose down              # Stop containers (data preserved)
docker compose down -v           # Stop AND delete volumes (data lost!)

Database Backups

Create a backup

docker exec mopc-postgres pg_dump -U mopc mopc | gzip > backup_$(date +%Y%m%d_%H%M%S).sql.gz

Restore a backup

gunzip < backup_20260130_020000.sql.gz | docker exec -i mopc-postgres psql -U mopc mopc

Set up daily backups (cron)

sudo mkdir -p /data/backups/mopc

cat > /opt/mopc/scripts/backup-db.sh << 'SCRIPT'
#!/bin/bash
BACKUP_DIR=/data/backups/mopc
DATE=$(date +%Y%m%d_%H%M%S)
docker exec mopc-postgres pg_dump -U mopc mopc | gzip > $BACKUP_DIR/mopc_$DATE.sql.gz
find $BACKUP_DIR -name "mopc_*.sql.gz" -mtime +30 -delete
SCRIPT

chmod +x /opt/mopc/scripts/backup-db.sh
echo "0 2 * * * /opt/mopc/scripts/backup-db.sh >> /var/log/mopc-backup.log 2>&1" | sudo tee /etc/cron.d/mopc-backup

Architecture

Gitea CI (Ubuntu runner)
  |
  v (docker push)
Container Registry
  |
  v (docker pull)
Linux VPS
  |
  v
Nginx (host, port 443) -- SSL termination
  |
  v
mopc-app (Docker, port 7600) -- Next.js standalone
  |
  v
mopc-postgres (Docker, port 5432) -- PostgreSQL 16

External services (separate Docker stacks):
  - MinIO (port 9000) -- S3-compatible file storage
  - Poste.io (port 587) -- SMTP email

Troubleshooting

App won't start

cd /opt/mopc/docker
docker compose logs app
docker compose exec postgres pg_isready -U mopc

Can't pull image

# Re-authenticate with registry
docker login <your-registry-url>

# Check image exists
docker pull <your-registry-url>/mopc-app:latest

Migration fails

# Check migration status
docker compose exec app npx prisma migrate status

# Reset (DESTROYS DATA):
docker compose exec app npx prisma migrate reset

SSL certificate issues

sudo certbot certificates
sudo certbot renew --force-renewal

Port conflict

The app runs on port 7600. If something else uses it:

sudo ss -tlnp | grep 7600

Security Checklist

  • SSL certificate active and auto-renewing
  • docker/.env has strong, unique passwords
  • NEXTAUTH_SECRET is randomly generated
  • Gitea registry credentials secured
  • Firewall allows only ports 80, 443, 22
  • Docker daemon not exposed to network
  • Daily backups configured
  • Nginx security headers active