Prerequisites
- Ubuntu 22.04 LTS VPS (minimum 2 vCPU, 4 GB RAM recommended)
- A domain name pointed to your server's IP (A record:
app.yourdomain.com,api.yourdomain.com) - Ports 80 and 443 open in your firewall
1. Install Docker
SSH into your server and install Docker:
bash
# Install Docker
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker
# Verify
docker --version
docker compose version2. Clone the Repository
bash
git clone https://github.com/phaseflag/phaseflag.git /opt/phaseflag
cd /opt/phaseflag3. Configure Environment Variables
bash
cp infra/docker/.env.example infra/docker/.env.productionEdit /opt/phaseflag/infra/docker/.env.production:
bash
# Deployment mode
PHASEFLAG_DEPLOYMENT_MODE=saas
# Database — use an external managed PostgreSQL for production
PHASEFLAG_DATABASE_URL=postgresql+asyncpg://phaseflag:STRONG_PASSWORD@postgres:5432/phaseflag
# Strong secrets (generate with: openssl rand -hex 32)
PHASEFLAG_JWT_SECRET_KEY=<64-char-random-secret>
PHASEFLAG_API_SECRET_KEY=<64-char-random-secret>
# Your production domain
PHASEFLAG_CORS_ORIGINS=https://app.yourdomain.com,https://portal.yourdomain.com
# Email (optional — for password reset)
PHASEFLAG_SMTP_HOST=smtp.sendgrid.net
PHASEFLAG_SMTP_PORT=587
PHASEFLAG_SMTP_USER=apikey
PHASEFLAG_SMTP_PASSWORD=<sendgrid-api-key>
PHASEFLAG_FROM_EMAIL=noreply@yourdomain.com
PHASEFLAG_LOG_LEVEL=INFO4. Start the Stack with the Self-Hosted Compose File
bash
cd /opt/phaseflag/infra/docker
docker compose -f docker-compose.selfhosted.yml --env-file .env.production up -dRun migrations:
bash
docker compose -f docker-compose.selfhosted.yml exec api alembic upgrade head5. Configure nginx as a Reverse Proxy
Install nginx and Certbot:
bash
sudo apt-get install -y nginx certbot python3-certbot-nginxCreate the nginx configuration:
bash
sudo nano /etc/nginx/sites-available/phaseflagnginx
# API
server {
server_name api.yourdomain.com;
location / {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# SSE support (for real-time updates)
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 86400s;
}
}
# Dashboard
server {
server_name app.yourdomain.com;
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}Enable the site and obtain SSL certificates:
bash
sudo ln -s /etc/nginx/sites-available/phaseflag /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
# Obtain SSL certs via Let's Encrypt
sudo certbot --nginx -d api.yourdomain.com -d app.yourdomain.comCertbot will automatically modify your nginx config to add HTTPS and redirect HTTP to HTTPS.
6. Set Up Automatic Certificate Renewal
bash
sudo systemctl enable certbot.timer
sudo systemctl start certbot.timer
# Test renewal
sudo certbot renew --dry-run7. Configure a Systemd Service (Optional)
For automatic startup on boot:
bash
sudo nano /etc/systemd/system/phaseflag.serviceini
[Unit]
Description=Phase Flag
Requires=docker.service
After=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/phaseflag/infra/docker
ExecStart=/usr/bin/docker compose -f docker-compose.selfhosted.yml --env-file .env.production up -d
ExecStop=/usr/bin/docker compose -f docker-compose.selfhosted.yml down
TimeoutStartSec=0
[Install]
WantedBy=multi-user.targetbash
sudo systemctl enable phaseflag
sudo systemctl start phaseflagRelay Proxy
For high-throughput production deployments, run the relay proxy alongside the stack:
The docker-compose.selfhosted.yml includes a relay service. Set these variables in your .env.production:
bash
PHASEFLAG_RELAY_API_KEY=<relay-api-key-from-dashboard>
RELAY_PORT=8001Configure your SDKs to point to the relay instead of the control plane:
typescript
const client = new PhaseFlagClient({
apiKey: "sdk-prod-xxxxxxxxxxxx",
environment: "production",
baseUrl: "https://relay.yourdomain.com", // relay domain
});Updating Phase Flag
bash
cd /opt/phaseflag
git pull origin main
cd infra/docker
docker compose -f docker-compose.selfhosted.yml --env-file .env.production pull
docker compose -f docker-compose.selfhosted.yml --env-file .env.production up -d
# Run any new migrations
docker compose -f docker-compose.selfhosted.yml exec api alembic upgrade headBackup
Database backup
bash
docker compose -f docker-compose.selfhosted.yml exec postgres \
pg_dump -U phaseflag phaseflag | gzip > backup-$(date +%Y%m%d).sql.gzRestore
bash
gunzip -c backup-20260404.sql.gz | \
docker compose -f docker-compose.selfhosted.yml exec -T postgres \
psql -U phaseflag phaseflag