8.4 KiB
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:
# 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:
# 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
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
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
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
# 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
# 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:
# 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)
APP_PORT=80
# Container listens on port 80 directly
# Access at: http://your-vps-ip
Option 2: Custom Port (With Traefik/Nginx)
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:
# 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:
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
# 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
# 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
# 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
# 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:
# 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
# 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
# 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
# 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
- Set up monitoring (Prometheus + Grafana)
- Configure automated backups
- Add Slack/Discord notifications to pipeline
- Set up staging environment
- Implement blue-green deployments for zero downtime
Last Updated: 2026-01-29
Maintainer: Mike Fitzpatrick