MOPC-App/DEPLOYMENT.md

319 lines
6.7 KiB
Markdown

# 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, you pull the latest image and restart
### 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)
```bash
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
# Log out and back in
```
### Install Nginx & Certbot (if needed)
```bash
sudo apt update
sudo apt install nginx certbot python3-certbot-nginx
```
## First-Time Deployment
### 1. Clone the repository
```bash
git clone <your-repo-url> /opt/mopc
cd /opt/mopc
```
### 2. Configure environment variables
```bash
cp docker/.env.production docker/.env
nano docker/.env
```
Fill in all `CHANGE_ME` values. Generate secrets with:
```bash
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
```bash
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)
```bash
./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
```bash
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
```bash
sudo certbot --nginx -d portal.monaco-opc.com
```
Auto-renewal is configured by default. Test with:
```bash
sudo certbot renew --dry-run
```
### 7. Verify
```bash
curl https://portal.monaco-opc.com/api/health
```
Expected response:
```json
{"status":"healthy","timestamp":"...","services":{"database":"connected"}}
```
## Updating the Platform
After Gitea CI builds a new image (push to `main`):
```bash
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 Operations
### View logs
```bash
cd /opt/mopc/docker
docker compose logs -f app # App logs
docker compose logs -f postgres # Database logs
```
### Run migrations manually
```bash
cd /opt/mopc/docker
docker compose exec app npx prisma migrate deploy
```
### Open a shell in the app container
```bash
cd /opt/mopc/docker
docker compose exec app sh
```
### Restart services
```bash
cd /opt/mopc/docker
docker compose restart app # App only
docker compose restart # All services
```
### Stop everything
```bash
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
```bash
docker exec mopc-postgres pg_dump -U mopc mopc | gzip > backup_$(date +%Y%m%d_%H%M%S).sql.gz
```
### Restore a backup
```bash
gunzip < backup_20260130_020000.sql.gz | docker exec -i mopc-postgres psql -U mopc mopc
```
### Set up daily backups (cron)
```bash
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
```bash
cd /opt/mopc/docker
docker compose logs app
docker compose exec postgres pg_isready -U mopc
```
### Can't pull image
```bash
# Re-authenticate with registry
docker login <your-registry-url>
# Check image exists
docker pull <your-registry-url>/mopc-app:latest
```
### Migration fails
```bash
# 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
```bash
sudo certbot certificates
sudo certbot renew --force-renewal
```
### Port conflict
The app runs on port 7600. If something else uses it:
```bash
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