Files
landing/DEPLOYMENT.md

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

  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