# Deployment Guide Quick reference for setting up Woodpecker CI/CD deployment. ## Prerequisites - Gitea instance with Woodpecker CI configured - Private Docker registry (Docker Hub, GitHub Container Registry, Harbor, etc.) - Linode VPS with Docker installed - SSH access to VPS ## Step-by-Step Setup ### 1. Prepare VPS SSH into your Linode VPS and install Docker: ```bash # Update system sudo apt update && sudo apt upgrade -y # Install Docker curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh # Create deploy user (optional, recommended) sudo useradd -m -s /bin/bash deploy sudo usermod -aG docker deploy # Verify Docker works docker --version docker ps ``` ### 2. Generate SSH Key for Deployment On your local machine or CI server: ```bash # Generate SSH key pair ssh-keygen -t ed25519 -C "woodpecker-deploy" -f ~/.ssh/woodpecker_deploy # Copy public key to VPS ssh-copy-id -i ~/.ssh/woodpecker_deploy.pub deploy@your-vps-ip # Test connection ssh -i ~/.ssh/woodpecker_deploy deploy@your-vps-ip "echo 'Connection successful'" # Get private key content (copy this to Woodpecker secret) cat ~/.ssh/woodpecker_deploy ``` ### 3. Configure Docker Registry **Option A: Docker Hub** ```bash REGISTRY_URL=docker.io REGISTRY_REPO=docker.io/yourusername/mifi-ventures-landing REGISTRY_USERNAME=yourusername # Password: Use Docker Hub access token (not your account password) # Generate at: https://hub.docker.com/settings/security ``` **Option B: GitHub Container Registry** ```bash REGISTRY_URL=ghcr.io REGISTRY_REPO=ghcr.io/yourusername/mifi-ventures-landing REGISTRY_USERNAME=yourusername # Password: GitHub Personal Access Token with write:packages scope # Generate at: https://github.com/settings/tokens ``` **Option C: Self-hosted Registry** ```bash REGISTRY_URL=registry.example.com REGISTRY_REPO=registry.example.com/mifi-ventures-landing REGISTRY_USERNAME=yourusername # Password: Your registry password or token ``` ### 4. Configure Woodpecker Secrets Navigate to: `Gitea → Repository → Settings → Secrets` Add these secrets (click "Add secret" for each): | Name | Value | Notes | |------|-------|-------| | `registry_password` | Your registry password/token | From step 3 | | `deploy_host` | `123.45.67.89` | Your Linode VPS IP | | `deploy_username` | `deploy` | SSH user from step 1 | | `deploy_ssh_key` | `-----BEGIN OPENSSH...` | Private key from step 2 | | `deploy_port` | `22` | Default SSH port | **Important**: For `deploy_ssh_key`, paste the entire private key including: ``` -----BEGIN OPENSSH PRIVATE KEY----- ...entire key content... -----END OPENSSH PRIVATE KEY----- ``` ### 5. Configure Woodpecker Variables Navigate to: `Gitea → Repository → Settings → Variables` Add these variables: | Name | Value | Example | |------|-------|---------| | `REGISTRY_URL` | Registry base URL | `ghcr.io` | | `REGISTRY_REPO` | Full image path | `ghcr.io/username/mifi-ventures-landing` | | `REGISTRY_USERNAME` | Registry username | `yourusername` | | `CONTAINER_NAME` | Container name | `mifi-ventures-landing` | | `APP_PORT` | Host port | `8080` | ### 6. Test Deployment #### Local Test Build ```bash # Clone repo git clone https://gitea.example.com/you/mifi-ventures-landing.git cd mifi-ventures-landing # Build locally docker build -t test . # Run locally docker run -d -p 8080:80 --name test test # Test curl http://localhost:8080 # Cleanup docker stop test && docker rm test ``` #### Trigger CI/CD ```bash # Make a small change echo "# Test deployment" >> README.md # Commit and push to main git add README.md git commit -m "test: trigger deployment" git push origin main # Watch pipeline in Woodpecker UI # Check: Gitea → Repository → Pipelines ``` ### 7. Verify Deployment After pipeline completes: ```bash # Check container is running ssh deploy@your-vps-ip "docker ps | grep mifi-ventures-landing" # Check container logs ssh deploy@your-vps-ip "docker logs mifi-ventures-landing" # Test HTTP response curl http://your-vps-ip:8080 # Check from browser # Visit: http://your-vps-ip:8080 ``` ## Port Configuration The pipeline uses `APP_PORT` to expose the container. Choose based on your setup: ### Option 1: Direct Port 80 (No Reverse Proxy) ```bash APP_PORT=80 # Container listens on port 80 directly # Access at: http://your-vps-ip ``` ### Option 2: Custom Port (With Traefik/Nginx) ```bash APP_PORT=8080 # Container listens on 8080 # Traefik/Nginx proxies from 80/443 → 8080 # Access via domain: https://mifi.ventures ``` **Note**: With Traefik, security headers are added at the proxy level. ## Traefik Integration Example If using Traefik as reverse proxy, add labels to container: ```yaml # In docker-compose.yml or docker run command labels: - "traefik.enable=true" - "traefik.http.routers.mifi.rule=Host(`mifi.ventures`)" - "traefik.http.routers.mifi.entrypoints=websecure" - "traefik.http.routers.mifi.tls.certresolver=letsencrypt" - "traefik.http.services.mifi.loadbalancer.server.port=80" ``` Update Woodpecker deploy script to include labels: ```bash docker run -d \ --name $CONTAINER_NAME \ --restart unless-stopped \ --label "traefik.enable=true" \ --label "traefik.http.routers.mifi.rule=Host(\`mifi.ventures\`)" \ --label "traefik.http.routers.mifi.entrypoints=websecure" \ --label "traefik.http.routers.mifi.tls.certresolver=letsencrypt" \ --label "traefik.http.services.mifi.loadbalancer.server.port=80" \ --network traefik-public \ $REGISTRY_REPO:latest ``` ## Common Issues ### SSH Permission Denied ```bash # Check key permissions (must be 600) chmod 600 ~/.ssh/woodpecker_deploy # Verify public key is on server ssh deploy@host "cat ~/.ssh/authorized_keys" # Test with verbose output ssh -vvv -i ~/.ssh/woodpecker_deploy deploy@host ``` ### Registry Login Failed ```bash # Test login locally echo "PASSWORD" | docker login registry.example.com -u username --password-stdin # For GitHub: Ensure token has write:packages scope # For Docker Hub: Use access token, not password ``` ### Container Won't Start ```bash # Check logs ssh deploy@host "docker logs mifi-ventures-landing" # Check for port conflicts ssh deploy@host "netstat -tulpn | grep :80" # Verify image pulled successfully ssh deploy@host "docker images | grep mifi-ventures" ``` ### Health Check Fails ```bash # Check nginx is running ssh deploy@host "docker exec mifi-ventures-landing ps aux" # Test internally ssh deploy@host "docker exec mifi-ventures-landing wget -O- http://localhost/" # Check if firewall blocking ssh deploy@host "ufw status" ``` ## Rollback Procedure If deployment fails: ```bash # SSH to VPS ssh deploy@your-vps-ip # List available images docker images | grep mifi-ventures # Find previous working SHA # (from Gitea commit history or Docker labels) # Stop current container docker stop mifi-ventures-landing docker rm mifi-ventures-landing # Start previous version docker run -d \ --name mifi-ventures-landing \ --restart unless-stopped \ -p 8080:80 \ registry.example.com/mifi-ventures-landing:PREVIOUS_SHA # Verify docker ps | grep mifi-ventures-landing curl http://localhost:8080 ``` ## Monitoring ### Container Status ```bash # Check if running docker ps | grep mifi-ventures-landing # View logs (last 100 lines) docker logs --tail 100 mifi-ventures-landing # Follow logs in real-time docker logs -f mifi-ventures-landing # Check resource usage docker stats mifi-ventures-landing ``` ### Nginx Access Logs ```bash # View access logs docker exec mifi-ventures-landing tail -f /var/log/nginx/access.log # View error logs docker exec mifi-ventures-landing tail -f /var/log/nginx/error.log ``` ### Disk Space ```bash # Check Docker disk usage docker system df # Cleanup old images (automatic in pipeline, or manual) docker image prune -af --filter "until=72h" # Full cleanup (careful!) docker system prune -a --volumes ``` ## Security Checklist - [ ] SSH key is Ed25519 (not RSA) - [ ] Private key is stored securely in Woodpecker (never in repo) - [ ] Deploy user has minimal permissions (not root if possible) - [ ] Registry uses authentication (not public) - [ ] VPS firewall configured (ufw or iptables) - [ ] Traefik handles TLS termination (if used) - [ ] Container runs as non-root user (nginx user) - [ ] Regular security updates (`apt update && apt upgrade`) ## Next Steps 1. Set up monitoring (Prometheus + Grafana) 2. Configure automated backups 3. Add Slack/Discord notifications to pipeline 4. Set up staging environment 5. Implement blue-green deployments for zero downtime --- **Last Updated**: 2026-01-29 **Maintainer**: Mike Fitzpatrick