319 lines
6.7 KiB
Markdown
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
|