16 KiB
Production Deployment - Complete Setup Guide
Status: 🚧 In Progress Last Updated: 2025-10-30 Target Server: 94.16.110.151 (Netcup)
Overview
This guide walks through the complete setup of production deployment from scratch, covering:
- Gitea Runner (Development Machine)
- Ansible Vault Secrets
- Production Server Initial Setup
- CI/CD Pipeline Testing
- Monitoring & Health Checks
Prerequisites
Development Machine:
- ✅ Docker & Docker Compose installed
- ✅ Ansible installed (
pip install ansible) - ✅ SSH key for production server
- ✅ Access to Gitea admin panel
Production Server (94.16.110.151):
- ✅ Docker & Docker Compose installed
- ✅ User
deploycreated with Docker permissions - ✅ SSH access configured
- ✅ Firewall configured (ports 80, 443, 2222)
Phase 1: Gitea Runner Setup (Development Machine)
Step 1.1: Get Gitea Registration Token
-
Navigate to Gitea admin panel:
https://git.michaelschiemer.de/admin/actions/runners -
Click "Create New Runner"
-
Copy the registration token (format:
<long-random-string>)
Step 1.2: Configure Runner Environment
cd deployment/gitea-runner
# Copy environment template
cp .env.example .env
# Edit configuration
nano .env
Required Configuration in .env:
# Gitea Instance URL
GITEA_INSTANCE_URL=https://git.michaelschiemer.de
# Registration Token (from Step 1.1)
GITEA_RUNNER_REGISTRATION_TOKEN=<your-token-from-gitea>
# Runner Name (appears in Gitea UI)
GITEA_RUNNER_NAME=dev-runner-01
# Runner Labels (environments this runner supports)
GITEA_RUNNER_LABELS=ubuntu-latest:docker://node:20-bullseye,ubuntu-22.04:docker://catthehacker/ubuntu:act-22.04
# Runner Capacity (concurrent jobs)
GITEA_RUNNER_CAPACITY=1
# Docker-in-Docker settings
DOCKER_HOST=tcp://docker-dind:2376
DOCKER_TLS_VERIFY=1
Step 1.3: Register and Start Runner
# Register runner with Gitea
./register.sh
# Expected output:
# ✅ Starting Gitea Runner services...
# ✅ Runner registered successfully
# ✅ Runner is now active
# Verify runner is running
docker compose ps
# Check logs
docker compose logs -f gitea-runner
Step 1.4: Verify Runner in Gitea
- Go to: https://git.michaelschiemer.de/admin/actions/runners
- You should see
dev-runner-01listed as "Idle" or "Active" - Status should be green/online
✅ Checkpoint: Runner visible in Gitea UI and showing as "Idle"
Phase 2: Ansible Vault Secrets Setup
Step 2.1: Create Vault Password
cd deployment/ansible/secrets
# Create vault password file (gitignored)
echo "your-secure-vault-password-here" > .vault_pass
# Secure the file
chmod 600 .vault_pass
⚠️ IMPORTANT: Store this vault password in your password manager! You'll need it for all Ansible operations.
Step 2.2: Create Production Secrets File
# Copy example template
cp production.vault.yml.example production.vault.yml
# Edit with your actual secrets
nano production.vault.yml
Required Secrets in production.vault.yml:
---
# Docker Registry Credentials
docker_registry_user: "admin"
docker_registry_password: "your-registry-password"
# Application Environment Variables
app_key: "base64:generated-32-character-key"
app_env: "production"
app_debug: "false"
# Database Credentials
db_host: "postgres"
db_port: "5432"
db_name: "framework_production"
db_user: "framework_user"
db_password: "your-secure-db-password"
# Redis Configuration
redis_host: "redis"
redis_port: "6379"
redis_password: "your-secure-redis-password"
# Cache Configuration
cache_driver: "redis"
cache_prefix: "framework"
# Queue Configuration
queue_connection: "redis"
queue_name: "default"
# Session Configuration
session_driver: "redis"
session_lifetime: "120"
# Encryption Keys
encryption_key: "base64:your-32-byte-encryption-key"
state_encryption_key: "base64:your-32-byte-state-encryption-key"
# SMTP Configuration (Optional)
mail_mailer: "smtp"
mail_host: "smtp.example.com"
mail_port: "587"
mail_username: "noreply@michaelschiemer.de"
mail_password: "your-smtp-password"
mail_encryption: "tls"
mail_from_address: "noreply@michaelschiemer.de"
mail_from_name: "Framework"
# Admin IPs (comma-separated)
admin_allowed_ips: "127.0.0.1,::1"
# Rate Limiting
rate_limit_enabled: "true"
rate_limit_default: "60"
rate_limit_window: "60"
Step 2.3: Generate Encryption Keys
# Generate app_key (32 bytes base64)
php -r "echo 'base64:' . base64_encode(random_bytes(32)) . PHP_EOL;"
# Generate encryption_key (32 bytes base64)
php -r "echo 'base64:' . base64_encode(random_bytes(32)) . PHP_EOL;"
# Generate state_encryption_key (32 bytes base64)
php -r "echo 'base64:' . base64_encode(random_bytes(32)) . PHP_EOL;"
# Copy these values into production.vault.yml
Step 2.4: Encrypt Secrets File
# Encrypt the secrets file
ansible-vault encrypt production.vault.yml \
--vault-password-file .vault_pass
# Verify encryption worked
file production.vault.yml
# Should output: production.vault.yml: ASCII text
# View encrypted content (should show encrypted data)
cat production.vault.yml
# Test decryption (view content)
ansible-vault view production.vault.yml \
--vault-password-file .vault_pass
✅ Checkpoint: production.vault.yml is encrypted and can be decrypted with vault password
Phase 3: Production Server Initial Setup
Step 3.1: Deploy Infrastructure Stacks
On Production Server (SSH as deploy user):
# SSH to production server
ssh deploy@94.16.110.151
# Navigate to stacks directory
cd ~/deployment/stacks
# Deploy stacks in order
# 1. Traefik (Reverse Proxy & SSL)
cd traefik
docker compose up -d
docker compose logs -f
# Wait for "Configuration loaded" message
# Ctrl+C to exit logs
# 2. PostgreSQL (Database)
cd ../postgresql
docker compose up -d
docker compose logs -f
# Wait for "database system is ready to accept connections"
# Ctrl+C to exit logs
# 3. Docker Registry (Private Registry)
cd ../registry
docker compose up -d
docker compose logs -f
# Wait for "listening on [::]:5000"
# Ctrl+C to exit logs
# 4. Gitea (Git Server + MySQL + Redis)
cd ../gitea
docker compose up -d
docker compose logs -f
# Wait for "Listen: http://0.0.0.0:3000"
# Ctrl+C to exit logs
# 5. Monitoring (Portainer + Grafana + Prometheus)
cd ../monitoring
docker compose up -d
docker compose logs -f
# Wait for all services to start
# Ctrl+C to exit logs
# Verify all stacks are running
docker ps
Step 3.2: Configure Gitea
- Access Gitea: https://git.michaelschiemer.de
- Complete initial setup wizard:
- Database: Use MySQL from stack
- Admin account: Create admin user
- Repository root:
/data/git/repositories - Enable Actions in admin settings
Step 3.3: Create Docker Registry User
# SSH to production server
ssh deploy@94.16.110.151
# Create registry htpasswd entry
cd ~/deployment/stacks/registry
docker compose exec registry htpasswd -Bbn admin your-registry-password >> auth/htpasswd
# Test login
docker login git.michaelschiemer.de:5000
# Username: admin
# Password: your-registry-password
Step 3.4: Setup SSH Keys for Ansible
On Development Machine:
# Generate SSH key if not exists
ssh-keygen -t ed25519 -f ~/.ssh/production -C "ansible-deploy"
# Copy public key to production server
ssh-copy-id -i ~/.ssh/production.pub deploy@94.16.110.151
# Test SSH connection
ssh -i ~/.ssh/production deploy@94.16.110.151 "echo 'SSH works!'"
✅ Checkpoint: All infrastructure stacks running, SSH access configured
Phase 4: Deploy Application Secrets
Step 4.1: Deploy Secrets to Production
On Development Machine:
cd deployment/ansible
# Test Ansible connectivity
ansible production -m ping
# Deploy secrets to production server
ansible-playbook playbooks/setup-production-secrets.yml \
--vault-password-file secrets/.vault_pass
# Expected output:
# PLAY [Deploy Production Secrets] ***
# TASK [Ensure secrets directory exists] *** ok
# TASK [Deploy environment file] *** changed
# PLAY RECAP *** production: ok=2 changed=1
Step 4.2: Verify Secrets Deployed
# SSH to production server
ssh deploy@94.16.110.151
# Check secrets directory
ls -la ~/secrets/
# Verify .env.production exists (do NOT cat - contains secrets!)
file ~/secrets/.env.production
# Should output: .env.production: ASCII text
# Check file permissions
stat ~/secrets/.env.production
# Should be 600 (readable only by deploy user)
✅ Checkpoint: Secrets deployed to production server in ~/secrets/.env.production
Phase 5: Setup Gitea Secrets for CI/CD
Step 5.1: Configure Repository Secrets
-
Go to repository settings in Gitea:
https://git.michaelschiemer.de/<username>/michaelschiemer/settings/secrets -
Add the following secrets:
REGISTRY_USER
adminREGISTRY_PASSWORD
<your-registry-password>SSH_PRIVATE_KEY
<content-of-~/.ssh/production>ANSIBLE_VAULT_PASSWORD
<your-vault-password-from-step-2.1>
Step 5.2: Verify Secrets in Gitea
- Check secrets are visible in repository settings
- Each secret should show "Hidden" value with green checkmark
✅ Checkpoint: All required secrets configured in Gitea repository
Phase 6: First Deployment Test
Step 6.1: Manual Deployment Dry-Run
On Development Machine:
cd deployment/ansible
# Test deployment (check mode - no changes)
ansible-playbook -i inventory/production.yml \
playbooks/deploy-update.yml \
-e "image_tag=test-$(date +%s)" \
-e "git_commit_sha=test123" \
-e "deployment_timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
-e "docker_registry_username=admin" \
-e "docker_registry_password=your-registry-password" \
--check
# Expected: Should show what would be changed
Step 6.2: Trigger CI/CD Pipeline
Option A: Push to main branch
# Make a small change (add comment to file)
echo "# Deployment test $(date)" >> deployment/DEPLOYMENT_TEST.txt
# Commit and push to main
git add deployment/DEPLOYMENT_TEST.txt
git commit -m "test(deployment): trigger CI/CD pipeline"
git push origin main
Option B: Manual trigger
- Go to Gitea repository: Actions tab
- Select workflow: "Production Deployment Pipeline"
- Click "Run workflow"
- Select branch: main
- Click "Run"
Step 6.3: Monitor Pipeline Execution
- Go to: https://git.michaelschiemer.de//michaelschiemer/actions
- Find the running workflow
- Click to view details
- Monitor each job:
- ✅ Test: Tests & quality checks pass
- ✅ Build: Docker image built and pushed
- ✅ Deploy: Application deployed to production
Step 6.4: Verify Deployment
# Test health endpoint
curl -k https://michaelschiemer.de/health
# Expected response:
# {"status":"healthy","timestamp":"2025-10-30T14:30:00Z"}
# Check application logs
ssh deploy@94.16.110.151 "docker compose -f ~/application/docker-compose.yml logs -f app-php"
✅ Checkpoint: CI/CD pipeline executed successfully, application running on production
Phase 7: Monitoring & Health Checks
Step 7.1: Access Monitoring Tools
Portainer
https://portainer.michaelschiemer.de
- View all running containers
- Monitor resource usage
- Check logs
Grafana
https://grafana.michaelschiemer.de
- Username: admin
- Password: (set during setup)
- View application metrics
- Setup alerts
Prometheus
https://prometheus.michaelschiemer.de
- Query metrics
- Check targets
- Verify scraping
Step 7.2: Configure Alerting
In Grafana:
- Go to Alerting > Contact points
- Add email notification channel
- Create alert rules:
- High CPU usage (>80% for 5 minutes)
- High memory usage (>80%)
- Application down (health check fails)
- Database connection failures
Step 7.3: Setup Health Check Monitoring
# Create cron job on production server
ssh deploy@94.16.110.151
# Add health check script
crontab -e
# Add line:
*/5 * * * * curl -f https://michaelschiemer.de/health || echo "Health check failed" | mail -s "Production Health Check Failed" admin@michaelschiemer.de
✅ Checkpoint: Monitoring tools accessible, alerts configured
Phase 8: Backup & Rollback Testing
Step 8.1: Verify Backups
# SSH to production server
ssh deploy@94.16.110.151
# Check backup directory
ls -lh ~/backups/
# Should see backup folders with timestamps
# Example: 2025-10-30T14-30-00/
Step 8.2: Test Rollback
# On development machine
cd deployment/ansible
# Rollback to previous version
ansible-playbook -i inventory/production.yml \
playbooks/rollback.yml
# Verify rollback worked
curl -k https://michaelschiemer.de/health
✅ Checkpoint: Backups created, rollback mechanism tested
Verification Checklist
Infrastructure
- Traefik running and routing HTTPS
- PostgreSQL accessible and accepting connections
- Docker Registry accessible at git.michaelschiemer.de:5000
- Gitea accessible at git.michaelschiemer.de
- Monitoring stack (Portainer, Grafana, Prometheus) running
Deployment
- Gitea Runner registered and showing "Idle" in UI
- Ansible Vault secrets encrypted and deployable
- SSH access configured for Ansible
- Repository secrets configured in Gitea
- CI/CD pipeline runs successfully end-to-end
Application
- Application accessible at https://michaelschiemer.de
- Health endpoint returns 200 OK
- Database migrations ran successfully
- Queue workers processing jobs
- Logs showing no errors
Monitoring
- Portainer shows all containers running
- Grafana dashboards displaying metrics
- Prometheus scraping all targets
- Alerts configured and sending notifications
Security
- All secrets encrypted with Ansible Vault
- SSH keys secured (600 permissions)
- Registry requires authentication
- HTTPS enforced on all public endpoints
- Firewall configured correctly
Troubleshooting
Gitea Runner Not Registering
Symptoms: Runner not appearing in Gitea UI after running ./register.sh
Solutions:
# Check runner logs
docker compose logs gitea-runner
# Verify registration token is correct
nano .env
# Check GITEA_RUNNER_REGISTRATION_TOKEN
# Unregister and re-register
./unregister.sh
./register.sh
Ansible Connection Failed
Symptoms: Failed to connect to the host via ssh
Solutions:
# Test SSH manually
ssh -i ~/.ssh/production deploy@94.16.110.151
# Check SSH key permissions
chmod 600 ~/.ssh/production
# Verify SSH key is added to server
ssh-copy-id -i ~/.ssh/production.pub deploy@94.16.110.151
Docker Registry Authentication Failed
Symptoms: unauthorized: authentication required
Solutions:
# Verify credentials
docker login git.michaelschiemer.de:5000
# Username: admin
# Password: <your-registry-password>
# Check htpasswd file on server
ssh deploy@94.16.110.151 "cat ~/deployment/stacks/registry/auth/htpasswd"
Deployment Health Check Failed
Symptoms: Health check returns 404 or times out
Solutions:
# Check application logs
ssh deploy@94.16.110.151 "docker compose -f ~/application/docker-compose.yml logs app-php"
# Verify application stack is running
ssh deploy@94.16.110.151 "docker ps"
# Check Traefik routing
ssh deploy@94.16.110.151 "docker compose -f ~/deployment/stacks/traefik/docker-compose.yml logs"
Next Steps
After successful deployment:
- Configure DNS: Point michaelschiemer.de to 94.16.110.151
- SSL Certificates: Traefik will automatically request Let's Encrypt certificates
- Monitoring: Review Grafana dashboards and setup additional alerts
- Backups: Configure automated database backups
- Performance: Review application performance and optimize
- Documentation: Update team documentation with production procedures
Support Contacts
- Infrastructure Issues: Check Portainer logs
- Deployment Issues: Review Gitea Actions logs
- Application Issues: Check application logs in Portainer
- Emergency Rollback: Run
ansible-playbook playbooks/rollback.yml
Setup Status: 🚧 In Progress Next Action: Start with Phase 1 - Gitea Runner Setup