# 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: 1. Gitea Runner (Development Machine) 2. Ansible Vault Secrets 3. Production Server Initial Setup 4. CI/CD Pipeline Testing 5. Monitoring & Health Checks --- ## Prerequisites **Development Machine:** - ✅ Docker & Docker Compose installed - ✅ Ansible installed (`pip install ansible`) - ✅ SSH key for production server (`~/.ssh/production`) - ✅ Git SSH key configured (see Phase 0) - ✅ Access to Gitea admin panel **Production Server (94.16.110.151):** - ✅ Docker & Docker Compose installed - ✅ User `deploy` created with Docker permissions - ✅ SSH access configured - ✅ Firewall configured (ports 80, 443, 2222) --- ## Phase 0: Git Repository SSH Access Setup (Development Machine) ### Step 0.1: Generate Git SSH Key Create a separate SSH key specifically for Git operations (different from the production server SSH key): ```bash # Generate SSH key for Git ssh-keygen -t ed25519 -f ~/.ssh/git_michaelschiemer -C "git@michaelschiemer.de" -N "" # Set correct permissions chmod 600 ~/.ssh/git_michaelschiemer chmod 644 ~/.ssh/git_michaelschiemer.pub ``` ### Step 0.2: Configure SSH Config Add Git SSH configuration to `~/.ssh/config`: ```bash # Edit SSH config nano ~/.ssh/config ``` Add the following configuration: ``` Host git.michaelschiemer.de HostName git.michaelschiemer.de Port 2222 User git IdentityFile ~/.ssh/git_michaelschiemer StrictHostKeyChecking no UserKnownHostsFile /dev/null ``` ### Step 0.3: Add Public Key to Gitea 1. Display your public key: ```bash cat ~/.ssh/git_michaelschiemer.pub ``` 2. Copy the output (starts with `ssh-ed25519 ...`) 3. In Gitea: - Go to **Settings** → **SSH / GPG Keys** - Click **Add Key** - Paste the public key - Click **Add Key** 4. Verify the connection: ```bash ssh -T git@git.michaelschiemer.de ``` Expected output: `Hi there! You've successfully authenticated...` ### Step 0.4: Update Git Remote (if needed) If your `origin` remote uses HTTPS, switch it to SSH: ```bash # Check current remote URL git remote -v # Update to SSH git remote set-url origin git@git.michaelschiemer.de:michael/michaelschiemer.git # Test push (should work without password prompt) git push origin main ``` **Note**: This SSH key is separate from the production server SSH key (`~/.ssh/production`). The production key is used for Ansible/server access, while the Git key is only for repository operations. --- ## Phase 1: Gitea Runner Setup (Development Machine) ### Step 1.1: Get Gitea Registration Token 1. Navigate to Gitea admin panel: ``` https://git.michaelschiemer.de/admin/actions/runners ``` 2. Click **"Create New Runner"** 3. Copy the registration token (format: ``) ### Step 1.2: Configure Runner Environment ```bash cd deployment/gitea-runner # Copy environment template cp .env.example .env # Edit configuration nano .env ``` **Required Configuration in `.env`:** ```bash # Gitea Instance URL GITEA_INSTANCE_URL=https://git.michaelschiemer.de # Registration Token (from Step 1.1) GITEA_RUNNER_REGISTRATION_TOKEN= # 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 ```bash # 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 1. Go to: https://git.michaelschiemer.de/admin/actions/runners 2. You should see `dev-runner-01` listed as **"Idle"** or **"Active"** 3. 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 ```bash 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 ```bash # 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`:** ```yaml --- # 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 ```bash # 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 ```bash # 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 ### Prerequisites Before running Phase 3, ensure: - ✅ SSH access to production server configured (`~/.ssh/production`) - ✅ Repository cloned on production server at `~/deployment/stacks` (or adjust `stacks_base_path` in playbook) - ✅ Ansible installed on your development machine: `pip install ansible` - ✅ Ansible collections installed: `ansible-galaxy collection install community.docker` ### Step 3.1: Clone Repository on Production Server (if not already done) **On Production Server:** ```bash # SSH to production server ssh deploy@94.16.110.151 # Clone repository (if not already present) mkdir -p ~/deployment cd ~/deployment git clone git@git.michaelschiemer.de:michael/michaelschiemer.git . || git clone https://git.michaelschiemer.de/michael/michaelschiemer.git . ``` ### Step 3.2: Deploy Infrastructure Stacks with Ansible **On Development Machine:** ```bash # Navigate to Ansible directory cd deployment/ansible # Run infrastructure deployment playbook ansible-playbook playbooks/setup-infrastructure.yml \ -i inventory/production.yml # The playbook will: # 1. Create required Docker networks (traefik-public, app-internal) # 2. Deploy Traefik (Reverse Proxy & SSL) # 3. Deploy PostgreSQL (Database) # 4. Deploy Docker Registry (Private Registry) # 5. Deploy Gitea (Git Server + PostgreSQL) # 6. Deploy Monitoring (Portainer + Grafana + Prometheus) # 7. Wait for all services to be healthy # 8. Verify accessibility ``` **Expected output:** - ✅ All stacks deployed successfully - ✅ All services healthy - ✅ Gitea accessible at https://git.michaelschiemer.de **Note:** If monitoring passwords need to be stored in Vault (recommended for production), add them to `secrets/production.vault.yml`: - `vault_grafana_admin_password` - `vault_prometheus_password` Then run the playbook with vault: ```bash ansible-playbook playbooks/setup-infrastructure.yml \ -i inventory/production.yml \ --vault-password-file secrets/.vault_pass ``` ### Step 3.3: Configure Gitea (Manual Step) 1. Access Gitea: https://git.michaelschiemer.de 2. Complete initial setup wizard (first-time only): - **Database Type**: PostgreSQL - **Database Host**: `postgres:5432` - **Database User**: `gitea` - **Database Password**: `gitea_password` (or check `deployment/stacks/gitea/docker-compose.yml`) - **Database Name**: `gitea` - **Admin Account**: Create your admin user - **Repository Root**: `/data/git/repositories` (default) 3. **Enable Actions** (required for Phase 1): - Go to **Site Administration** → **Actions** - Enable **Enable Actions** checkbox - Save settings ### Step 3.4: Verify Docker Registry The Ansible playbook automatically creates registry authentication. To retrieve credentials: ```bash # SSH to production server ssh deploy@94.16.110.151 # View registry htpasswd (contains username:password hash) cat ~/deployment/stacks/registry/auth/htpasswd # The default username is 'admin' # Password hash can be used to login, or create new user: cd ~/deployment/stacks/registry docker compose exec registry htpasswd -Bbn >> auth/htpasswd docker compose restart registry # Test login docker login registry.michaelschiemer.de # Or if using port: docker login registry.michaelschiemer.de ``` **✅ Checkpoint**: All infrastructure stacks running, Gitea accessible, Actions enabled --- ## Phase 4: Deploy Application Secrets ### Step 4.1: Deploy Secrets to Production **On Development Machine:** ```bash 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 ```bash # 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 1. Go to repository settings in Gitea: ``` https://git.michaelschiemer.de//michaelschiemer/settings/secrets ``` 2. Add the following secrets: **REGISTRY_USER** ``` admin ``` **REGISTRY_PASSWORD** ``` ``` **SSH_PRIVATE_KEY** ``` ``` **ANSIBLE_VAULT_PASSWORD** ``` ``` ### Step 5.2: Verify Secrets in Gitea 1. Check secrets are visible in repository settings 2. 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:** ```bash 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** ```bash # 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** 1. Go to Gitea repository: Actions tab 2. Select workflow: "Production Deployment Pipeline" 3. Click "Run workflow" 4. Select branch: main 5. Click "Run" ### Step 6.3: Monitor Pipeline Execution 1. Go to: https://git.michaelschiemer.de//michaelschiemer/actions 2. Find the running workflow 3. Click to view details 4. Monitor each job: - ✅ Test: Tests & quality checks pass - ✅ Build: Docker image built and pushed - ✅ Deploy: Application deployed to production ### Step 6.4: Verify Deployment ```bash # 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:** 1. Go to Alerting > Contact points 2. Add email notification channel 3. 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 ```bash # 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 ```bash # 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 ```bash # 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 registry.michaelschiemer.de - [ ] 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**: ```bash # 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**: ```bash # 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**: ```bash # Verify credentials docker login registry.michaelschiemer.de # Username: admin # 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**: ```bash # 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: 1. **Configure DNS**: Point michaelschiemer.de to 94.16.110.151 2. **SSL Certificates**: Traefik will automatically request Let's Encrypt certificates 3. **Monitoring**: Review Grafana dashboards and setup additional alerts 4. **Backups**: Configure automated database backups 5. **Performance**: Review application performance and optimize 6. **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