OpportunityDAO - Docker Deployment Guide
This guide covers deploying OpportunityDAO to EC2 with Docker, Apache, and GitLab CI/CD.
Architecture
GitLab (Self-hosted) → Push to main
↓
GitLab CI/CD Pipeline
↓
EC2 Instance (Ubuntu 24 + Virtualmin + Apache)
├── Docker Container: opportunitydao-app (Next.js on port 6007)
├── Docker Container: opportunitydao-processor (Background job)
└── Apache → Reverse Proxy → Docker Container
Prerequisites
On EC2 Instance
- Install Docker & Docker Compose
# Install Docker
sudo apt update
sudo apt install -y docker.io docker-compose
sudo systemctl enable docker
sudo systemctl start docker
# Add user to docker group (replace 'ubuntu' with your user)
sudo usermod -aG docker $USER
newgrp docker
# Verify installation
docker --version
docker-compose --version
- Create deployment directory
sudo mkdir -p /var/www/opportunitydao
sudo chown $USER:$USER /var/www/opportunitydao
- Setup environment variables
# Create .env file
cat > /var/www/opportunitydao/.env <<'EOF'
# Database
DATABASE_URL="postgresql://user:pass@hostip:5432/opportunitydao"
# JWT
JWT_SECRET="your-secret-key-change-this"
# Public URL
NEXT_PUBLIC_API_URL="https://opportunitydao.app"
# Optional: Blockchain RPC endpoints
BSC_RPC_URL=""
ETH_RPC_URL=""
POLYGON_RPC_URL=""
BLOCKFROST_API_KEY=""
BLOCKFROST_NETWORK="mainnet"
EOF
chmod 600 /var/www/opportunitydao/.env
Apache Configuration
Quick Setup
The application runs on port 6007 with host network mode, making Apache configuration straightforward.
Enable required modules:
sudo a2enmod proxy
sudo a2enmod proxy_http
sudo a2enmod proxy_wstunnel
sudo a2enmod headers
sudo systemctl restart apache2
*Key configuration (add to your VirtualHost :443):
ProxyPreserveHost On
ProxyPass / http://localhost:6007/
ProxyPassReverse / http://localhost:6007/
# WebSocket support
RewriteEngine on
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule ^/?(.*) "ws://localhost:6007/$1" [P,L]
Security Headers (recommended): Add to your VirtualHost configuration for enhanced security:
Header set X-Frame-Options "SAMEORIGIN"
Header set X-Content-Type-Options "nosniff"
Header set X-XSS-Protection "1; mode=block"
Setup SSL with Let's Encrypt
If using Virtualmin:
- Use Virtualmin's SSL certificate interface
Manual setup:
sudo apt install certbot python3-certbot-apache
sudo certbot --apache -d opportunitydao.com -d www.opportunitydao.com
GitLab CI/CD Setup
1. GitLab Variables
In your GitLab project: Settings → CI/CD → Variables
| Variable | Value | Protected | Masked |
|---|---|---|---|
SSH_PRIVATE_KEY | EC2 SSH private key | ✓ | ✓ |
EC2_HOST | EC2 IP or domain | ✓ | - |
EC2_USER | ubuntu or your user | ✓ | - |
2. SSH Key Setup
# On your local machine
ssh-keygen -t ed25519 -C "gitlab-ci-opportunitydao" -f ~/.ssh/gitlab-ci-opportunitydao
# Copy public key to EC2
ssh-copy-id -i ~/.ssh/gitlab-ci-opportunitydao.pub $EC2_USER@$EC2_HOST
# Add private key to GitLab CI/CD Variables
cat ~/.ssh/gitlab-ci-opportunitydao
3. GitLab Runner (Self-hosted GitLab)
If using self-hosted GitLab, ensure you have a runner with Docker executor:
# On GitLab runner machine
sudo gitlab-runner register
# Choose 'docker' as executor
# Use 'docker:24' as default image
Deployment
Initial Deployment
# On EC2, manually do first deployment
cd /var/www/opportunitydao
git pull origin main
# Copy files to deployment directory
rsync -av --exclude='.git' --exclude='node_modules' --exclude='.next' \
./ /var/www/opportunitydao/
cd /var/www/opportunitydao
# Start containers
docker-compose up -d --build
# Run database migrations
docker-compose exec app npx prisma migrate deploy
# Check status
docker-compose ps
docker-compose logs -f
Subsequent Deployments (GitLab CI/CD)
- Push to
mainbranch - GitLab CI/CD pipeline starts automatically
- Manual approval required for deployment (change in
.gitlab-ci.ymlif you want automatic) - Click "Deploy" button in GitLab pipeline
Management Commands
Docker Commands
cd /var/www/opportunitydao
# View logs
docker-compose logs -f # All services
docker-compose logs -f app # Next.js app only
docker-compose logs -f deposit-processor # Background job only
# Restart services
docker-compose restart
docker-compose restart app
# Stop services
docker-compose stop
docker-compose down # Stop and remove containers
# Start services
docker-compose up -d
# Rebuild and restart
docker-compose up -d --build
# Execute commands inside container
docker-compose exec app sh # Shell access
docker-compose exec app npx prisma migrate deploy # Run migrations
docker-compose exec app npx prisma studio # Open Prisma Studio
# View container status
docker-compose ps
# Remove old images/containers
docker system prune -f
Database Migrations
# Run migrations
docker-compose exec app npx prisma migrate deploy
# Generate Prisma client
docker-compose exec app npx prisma generate
# Reset database (DANGEROUS)
docker-compose exec app npx prisma migrate reset
Background Job
# View deposit processor logs
docker-compose logs -f deposit-processor
# Manually trigger deposit processing
docker-compose exec deposit-processor npx tsx lib/jobs/processDeposits.ts
# Restart processor
docker-compose restart deposit-processor
Monitoring
Health Check
# Check if app is responding
curl http://localhost:6007/
# Check from outside
curl https://opportunitydao.app/
Container Resources
# View resource usage
docker stats
# View specific container
docker stats opportunitydao-app
Apache Logs
# Access logs
sudo tail -f /var/log/apache2/opportunitydao_access.log
# Error logs
sudo tail -f /var/log/apache2/opportunitydao_error.log
Troubleshooting
Container won't start
# Check logs
docker-compose logs app
# Check if port 6007 is already in use
sudo netstat -tulpn | grep 6007
# Rebuild from scratch
docker-compose down
docker-compose build --no-cache
docker-compose up -d
Database connection errors
# Verify DATABASE_URL in .env
cat /var/www/opportunitydao/.env | grep DATABASE_URL
# Test database connection
docker-compose exec app npx prisma db pull
# Check if PostgreSQL is accessible
psql $DATABASE_URL -c "SELECT 1;"
Apache proxy not working
# Test Apache config
sudo apache2ctl configtest
# Check if modules are enabled
apache2ctl -M | grep proxy
# Restart Apache
sudo systemctl restart apache2
# Check Apache error logs
sudo tail -f /var/log/apache2/error.log
GitLab CI/CD pipeline fails
# Check SSH connection from GitLab runner
ssh $EC2_USER@$EC2_HOST "echo Connection successful"
# Verify GitLab variables are set
# Settings → CI/CD → Variables
# Check GitLab runner logs
sudo gitlab-runner verify
Rollback
Manual Rollback
cd /var/www/opportunitydao
# Pull previous commit
git checkout HEAD~1
docker-compose up -d --build
GitLab CI/CD Rollback
Use the "Rollback" job in GitLab pipeline (manual action)
Security Considerations
- Firewall: Only expose ports 80, 443, and 22
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 22/tcp
sudo ufw enable
- Environment variables: Never commit
.envto Git - SSH keys: Use dedicated keys for CI/CD, rotate regularly
- Docker: Keep Docker images updated
- SSL: Auto-renew with certbot
sudo certbot renew --dry-run
Performance Optimization
Docker Resource Limits
Edit docker-compose.yml:
services:
app:
# ... existing config
deploy:
resources:
limits:
cpus: '2'
memory: 2G
reservations:
cpus: '1'
memory: 1G
Apache Performance
Enable caching:
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpg "access plus 1 year"
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/png "access plus 1 year"
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
</IfModule>
Backup Strategy
# Backup script
#!/bin/bash
BACKUP_DIR="/var/backups/opportunitydao"
mkdir -p $BACKUP_DIR
# Backup database
docker-compose exec -T app npx prisma db pull > $BACKUP_DIR/schema-$(date +%Y%m%d).prisma
pg_dump $DATABASE_URL > $BACKUP_DIR/db-$(date +%Y%m%d).sql
# Backup application files
tar -czf $BACKUP_DIR/app-$(date +%Y%m%d).tar.gz /var/www/opportunitydao
# Keep only last 7 days
find $BACKUP_DIR -name "*.sql" -mtime +7 -delete
find $BACKUP_DIR -name "*.tar.gz" -mtime +7 -delete
Support
For issues:
- Check Docker logs:
docker-compose logs -f - Check Apache logs:
sudo tail -f /var/log/apache2/opportunitydao_error.log - Verify environment:
docker-compose config - Test connectivity:
docker-compose exec app sh