14 KiB
Production Deployment Guide
This guide walks you through deploying the Feuerwehr Dashboard in a production environment.
Table of Contents
- Server Requirements
- Pre-Deployment Checklist
- Deployment Steps
- Authentik Configuration
- SSL/HTTPS Setup
- Database Management
- Monitoring Setup
- Update Procedures
- Rollback Procedures
- Performance Tuning
- Security Hardening
Server Requirements
Minimum Requirements
- CPU: 2 cores
- RAM: 2 GB (4 GB recommended)
- Storage: 20 GB SSD
- OS: Ubuntu 20.04+ / Debian 11+ / CentOS 8+ / RHEL 8+
- Docker: 20.10+
- Docker Compose: 2.0+
Network Requirements
Open the following ports:
- 80 - HTTP (will redirect to HTTPS)
- 443 - HTTPS (if using SSL)
- 3000 - Backend API (can be internal only)
- 5432 - PostgreSQL (should be internal only)
Recommended Production Specs
- CPU: 4 cores
- RAM: 8 GB
- Storage: 50 GB SSD with RAID for database
- Network: 100 Mbps minimum
Pre-Deployment Checklist
Before deploying to production:
- Server meets minimum requirements
- Docker and Docker Compose installed
- Domain name configured (DNS A record pointing to server)
- Authentik instance available and accessible
- SSL certificates obtained (Let's Encrypt or commercial)
- Firewall configured
- Backup strategy planned
- Monitoring solution chosen
- All secrets and credentials prepared
Deployment Steps
Step 1: Server Preparation
Update system packages:
# Ubuntu/Debian
sudo apt update && sudo apt upgrade -y
# CentOS/RHEL
sudo yum update -y
Install Docker:
# Ubuntu/Debian
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER
# CentOS/RHEL
sudo yum install -y docker
sudo systemctl start docker
sudo systemctl enable docker
Install Docker Compose:
sudo curl -L "https://github.com/docker/compose/releases/download/v2.24.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose --version
Configure firewall:
# Ubuntu/Debian (UFW)
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
# CentOS/RHEL (firewalld)
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --reload
Step 2: Clone Repository
cd /opt
sudo git clone <your-repository-url> feuerwehr_dashboard
cd feuerwehr_dashboard
sudo chown -R $USER:$USER .
Step 3: Configure Environment
Generate secure secrets:
# Generate JWT secret (save this!)
openssl rand -base64 32
# Generate strong database password (save this!)
openssl rand -base64 24
Create .env file:
cp .env.example .env
nano .env
Configure the following critical variables:
# Database - Use strong passwords!
POSTGRES_DB=feuerwehr_prod
POSTGRES_USER=prod_user
POSTGRES_PASSWORD=<generated-secure-password>
POSTGRES_PORT=5432
# Backend
BACKEND_PORT=3000
NODE_ENV=production
# JWT - Use generated secret!
JWT_SECRET=<generated-jwt-secret>
# CORS - Set to your domain!
CORS_ORIGIN=https://dashboard.yourdomain.com
# Frontend
FRONTEND_PORT=80
# API URL - Set to your backend URL
VITE_API_URL=https://api.yourdomain.com
# Authentik OAuth (from Authentik setup)
AUTHENTIK_CLIENT_ID=<your-client-id>
AUTHENTIK_CLIENT_SECRET=<your-client-secret>
AUTHENTIK_ISSUER=https://auth.yourdomain.com/application/o/feuerwehr/
AUTHENTIK_REDIRECT_URI=https://dashboard.yourdomain.com/auth/callback
Secure the .env file:
chmod 600 .env
Step 4: Initial Deployment
Deploy the application:
make prod
# or
./deploy.sh production
Verify all services are running:
docker-compose ps
Expected output:
NAME STATUS PORTS
feuerwehr_backend_prod Up (healthy) 0.0.0.0:3000->3000/tcp
feuerwehr_db_prod Up (healthy) 0.0.0.0:5432->5432/tcp
feuerwehr_frontend_prod Up (healthy) 0.0.0.0:80->80/tcp
Check logs for errors:
make logs-prod
Step 5: Database Initialization
The database will be automatically initialized on first start using the migration scripts in backend/src/database/migrations/.
Verify database tables:
docker exec -it feuerwehr_db_prod psql -U prod_user -d feuerwehr_prod -c "\dt"
Step 6: Test the Deployment
Test health endpoints:
# Backend health check
curl http://localhost:3000/health
# Frontend availability
curl http://localhost:80
Test authentication flow:
- Open browser to
http://localhost(or your domain) - Click login
- Authenticate with Authentik
- Verify redirect back to dashboard
- Verify user profile loads
Authentik Configuration
See AUTHENTIK_SETUP.md for complete Authentik configuration.
Key points for production:
- Use HTTPS URLs for all redirect URIs
- Configure proper scopes:
openid,profile,email - Set token expiration appropriately (e.g., 3600s for access, 86400s for refresh)
- Enable required claims in the provider
- Test the flow thoroughly before going live
SSL/HTTPS Setup
Option 1: Using Caddy (Recommended)
Create Caddyfile:
dashboard.yourdomain.com {
reverse_proxy localhost:80
encode gzip
header {
Strict-Transport-Security "max-age=31536000;"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
Referrer-Policy "strict-origin-when-cross-origin"
}
}
api.yourdomain.com {
reverse_proxy localhost:3000
encode gzip
}
Run Caddy:
docker run -d \
--name caddy \
--network host \
-v $PWD/Caddyfile:/etc/caddy/Caddyfile \
-v caddy_data:/data \
-v caddy_config:/config \
--restart unless-stopped \
caddy:latest
Caddy will automatically obtain and renew SSL certificates from Let's Encrypt.
Option 2: Using Nginx with Certbot
Install Nginx and Certbot:
sudo apt install nginx certbot python3-certbot-nginx -y
Create Nginx configuration (/etc/nginx/sites-available/feuerwehr):
server {
listen 80;
server_name dashboard.yourdomain.com;
location / {
proxy_pass http://localhost:80;
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;
}
}
server {
listen 80;
server_name api.yourdomain.com;
location / {
proxy_pass http://localhost:3000;
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;
}
}
Enable and obtain SSL:
sudo ln -s /etc/nginx/sites-available/feuerwehr /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d dashboard.yourdomain.com -d api.yourdomain.com
Option 3: Using Docker with Traefik
See community guides for Traefik integration.
Database Management
Backup Procedures
Create automated backup script (/opt/backup-feuerwehr-db.sh):
#!/bin/bash
BACKUP_DIR="/opt/backups/feuerwehr"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/feuerwehr_backup_$DATE.sql.gz"
mkdir -p $BACKUP_DIR
docker exec feuerwehr_db_prod pg_dump -U prod_user feuerwehr_prod | gzip > $BACKUP_FILE
# Keep only last 30 days of backups
find $BACKUP_DIR -name "*.sql.gz" -mtime +30 -delete
echo "Backup completed: $BACKUP_FILE"
Make executable and schedule:
chmod +x /opt/backup-feuerwehr-db.sh
# Add to crontab (daily at 2 AM)
crontab -e
0 2 * * * /opt/backup-feuerwehr-db.sh >> /var/log/feuerwehr-backup.log 2>&1
Restore Procedures
Restore from backup:
# Stop backend service
docker-compose stop backend
# Restore database
gunzip -c /opt/backups/feuerwehr/feuerwehr_backup_20260223_020000.sql.gz | \
docker exec -i feuerwehr_db_prod psql -U prod_user -d feuerwehr_prod
# Restart backend
docker-compose start backend
Database Maintenance
Regular vacuum and analyze:
docker exec feuerwehr_db_prod psql -U prod_user -d feuerwehr_prod -c "VACUUM ANALYZE;"
Monitoring Setup
Basic Health Monitoring
Create monitoring script (/opt/monitor-feuerwehr.sh):
#!/bin/bash
# Check backend health
BACKEND_STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:3000/health)
if [ "$BACKEND_STATUS" != "200" ]; then
echo "Backend health check failed: $BACKEND_STATUS"
# Send alert (email, Slack, etc.)
fi
# Check database
DB_STATUS=$(docker exec feuerwehr_db_prod pg_isready -U prod_user)
if [ $? -ne 0 ]; then
echo "Database health check failed"
# Send alert
fi
# Check container status
CONTAINERS=$(docker-compose ps --services --filter "status=running" | wc -l)
if [ "$CONTAINERS" -lt 3 ]; then
echo "Not all containers are running"
# Send alert
fi
Schedule monitoring:
chmod +x /opt/monitor-feuerwehr.sh
crontab -e
*/5 * * * * /opt/monitor-feuerwehr.sh >> /var/log/feuerwehr-monitor.log 2>&1
Recommended Monitoring Tools
- Prometheus + Grafana - Metrics and dashboards
- Loki - Log aggregation
- Uptime Kuma - Simple uptime monitoring
- Portainer - Docker container management
Update Procedures
Standard Update Process
-
Backup everything:
/opt/backup-feuerwehr-db.sh docker-compose down tar -czf /opt/backups/feuerwehr_config_$(date +%Y%m%d).tar.gz .env docker-compose.yml -
Pull latest changes:
git pull origin main -
Review changes:
git log --oneline -10 git diff HEAD~1 .env.example -
Update environment if needed:
# Compare .env.example with your .env diff .env.example .env # Add any new required variables -
Rebuild and deploy:
make rebuild # or ./deploy.sh rebuild -
Verify update:
docker-compose ps make logs-prod # Test critical functionality
Zero-Downtime Updates
For zero-downtime updates, use blue-green deployment:
- Set up second environment
- Deploy new version to green
- Test green environment
- Switch traffic (using load balancer)
- Shut down blue environment
Rollback Procedures
Quick Rollback
If issues arise after update:
-
Stop current version:
docker-compose down -
Restore previous version:
git log --oneline -5 # Find previous commit git checkout <previous-commit-hash> -
Restore database if schema changed:
gunzip -c /opt/backups/feuerwehr/feuerwehr_backup_<timestamp>.sql.gz | \ docker exec -i feuerwehr_db_prod psql -U prod_user -d feuerwehr_prod -
Redeploy:
make prod
Tagged Releases
Use Git tags for stable releases:
# Tag a release
git tag -a v1.0.0 -m "Production release 1.0.0"
git push origin v1.0.0
# Deploy specific release
git checkout v1.0.0
make prod
Performance Tuning
Database Optimization
Edit PostgreSQL configuration in docker-compose.yml:
postgres:
command:
- "postgres"
- "-c"
- "max_connections=100"
- "-c"
- "shared_buffers=256MB"
- "-c"
- "effective_cache_size=1GB"
- "-c"
- "work_mem=4MB"
Backend Optimization
Add environment variables to backend:
# In .env
NODE_OPTIONS=--max-old-space-size=2048
Frontend Optimization
Already optimized:
- Vite build optimization
- Gzip compression in Nginx
- Static asset caching
Docker Resource Limits
Add resource limits to docker-compose.yml:
backend:
deploy:
resources:
limits:
cpus: '2'
memory: 2G
reservations:
cpus: '0.5'
memory: 512M
Security Hardening
Docker Security
Run containers as non-root:
backend:
user: "node"
postgres:
user: "postgres"
Network Security
Isolate database from external access:
postgres:
# Remove ports mapping for production
# ports:
# - "5432:5432"
# Database is only accessible via Docker network
Environment Security
Protect sensitive files:
chmod 600 .env
chmod 600 docker-compose.yml
Regular Security Updates
Set up automatic security updates:
# Ubuntu/Debian
sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
Security Monitoring
- Enable Docker security scanning
- Monitor logs for suspicious activity
- Set up fail2ban for SSH protection
- Regular security audits
Post-Deployment
After successful deployment:
- Document your setup - Note any custom configurations
- Train your team - Ensure team knows how to use the system
- Set up alerts - Configure notifications for issues
- Schedule maintenance - Plan for regular updates and backups
- Review security - Regular security audits
- Monitor performance - Track metrics and optimize as needed
Troubleshooting
Services Won't Start
Check logs:
docker-compose logs
Check resources:
docker stats
df -h
free -h
SSL Certificate Issues
Renew certificates:
sudo certbot renew
Performance Issues
Check resource usage:
docker stats
htop
iotop
Optimize database:
docker exec feuerwehr_db_prod psql -U prod_user -d feuerwehr_prod -c "VACUUM FULL ANALYZE;"
Support
For production support issues:
- Check logs first
- Review this deployment guide
- Consult TROUBLESHOOTING section
- Create detailed issue report
- Contact support team