Skip to main content

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

  1. 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
  1. Create deployment directory
sudo mkdir -p /var/www/opportunitydao
sudo chown $USER:$USER /var/www/opportunitydao
  1. 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

VariableValueProtectedMasked
SSH_PRIVATE_KEYEC2 SSH private key
EC2_HOSTEC2 IP or domain-
EC2_USERubuntu 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)

  1. Push to main branch
  2. GitLab CI/CD pipeline starts automatically
  3. Manual approval required for deployment (change in .gitlab-ci.yml if you want automatic)
  4. 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

  1. 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
  1. Environment variables: Never commit .env to Git
  2. SSH keys: Use dedicated keys for CI/CD, rotate regularly
  3. Docker: Keep Docker images updated
  4. 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