sharenet/CI_CD_PIPELINE_SETUP_GUIDE.md
continuist 41f0bd8c4f
Some checks are pending
CI/CD Pipeline / Test Backend (push) Waiting to run
CI/CD Pipeline / Test Frontend (push) Waiting to run
CI/CD Pipeline / Build and Push Docker Images (push) Blocked by required conditions
CI/CD Pipeline / Deploy to Production (push) Blocked by required conditions
Refactored to add various scripts to repository instead of have user manually create them
2025-06-28 11:43:57 -04:00

53 KiB

CI/CD Pipeline Setup Guide

This guide covers setting up a complete Continuous Integration/Continuous Deployment (CI/CD) pipeline with a CI/CD Linode and Production Linode for automated builds, testing, and deployments.

Architecture Overview

┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐
│   Forgejo Host  │    │   CI/CD Linode  │    │ Production Linode│
│   (Repository)  │    │ (Actions Runner)│    │ (Docker Deploy) │
│                 │    │ + Docker Registry│   │                 │
└─────────────────┘    └─────────────────┘    └─────────────────┘
         │                       │                       │
         │                       │                       │
         └─────────── Push ──────┼───────────────────────┘
                                 │
                                 └─── Deploy ────────────┘

Pipeline Flow

  1. Code Push: Developer pushes code to Forgejo repository
  2. Automated Testing: CI/CD Linode runs tests on backend and frontend
  3. Image Building: If tests pass, Docker images are built
  4. Registry Push: Images are pushed to private registry on CI/CD Linode
  5. Production Deployment: Production Linode pulls images and deploys
  6. Health Check: Application is verified and accessible

Prerequisites

  • Two Ubuntu 24.04 LTS Linodes with root access
  • Basic familiarity with Linux commands and SSH
  • Forgejo repository with Actions enabled
  • Optional: Domain name for Production Linode (for SSL/TLS)

Quick Start

  1. Set up CI/CD Linode (Steps 1-13)
  2. Set up Production Linode (Steps 14-26)
  3. Configure SSH key exchange (Step 27)
  4. Set up Forgejo repository secrets (Step 28)
  5. Test the complete pipeline (Step 29)

What's Included

CI/CD Linode Features

  • Forgejo Actions runner for automated builds
  • Local Docker registry for image storage
  • Registry web UI for image management
  • Automated cleanup of old images
  • Secure SSH communication with production

Production Linode Features

  • Docker-based application deployment
  • Optional SSL/TLS certificate management (if domain is provided)
  • Nginx reverse proxy with security headers
  • Automated backups and monitoring
  • Firewall and fail2ban protection

Pipeline Features

  • Automated testing on every code push
  • Automated image building and registry push
  • Automated deployment to production
  • Rollback capability with image versioning
  • Health monitoring and logging

Security Model and User Separation

This setup uses a principle of least privilege approach with separate users for different purposes:

User Roles

  1. Root User

    • Purpose: Initial system setup only
    • SSH Access: Disabled after setup
    • Privileges: Full system access (used only during initial configuration)
  2. Deployment User (DEPLOY_USER)

    • Purpose: SSH access, deployment tasks, system administration
    • SSH Access: Enabled with key-based authentication
    • Privileges: Sudo access for deployment and administrative tasks
    • Examples: deploy, ci, admin
  3. Service Account (SERVICE_USER)

    • Purpose: Running application services (Docker containers, databases)
    • SSH Access: None (no login shell)
    • Privileges: No sudo access, minimal system access
    • Examples: appuser, service, app

Security Benefits

  • No root SSH access: Eliminates the most common attack vector
  • Principle of least privilege: Each user has only the access they need
  • Separation of concerns: Deployment tasks vs. service execution are separate
  • Audit trail: Clear distinction between deployment and service activities
  • Reduced attack surface: Service account has minimal privileges

File Permissions

  • Application files: Owned by SERVICE_USER for security
  • Docker operations: Run by DEPLOY_USER with sudo (deployment only)
  • Service execution: Run by SERVICE_USER (no sudo needed)

Prerequisites and Initial Setup

What's Already Done (Assumptions)

This guide assumes you have already:

  1. Created two Ubuntu 24.04 LTS Linodes with root access
  2. Set root passwords for both Linodes
  3. Have SSH client installed on your local machine
  4. Have Forgejo repository with Actions enabled
  5. Optional: Domain name pointing to Production Linode's IP addresses

Step 0: Initial SSH Access and Verification

Before proceeding with the setup, you need to establish initial SSH access to both Linodes.

0.1 Get Your Linode IP Addresses

From your Linode dashboard, note the IP addresses for:

  • CI/CD Linode: YOUR_CI_CD_IP (IP address only, no domain needed)
  • Production Linode: YOUR_PRODUCTION_IP (IP address for SSH, domain for web access)

0.2 Test Initial SSH Access

Test SSH access to both Linodes:

# Test CI/CD Linode (IP address only)
ssh root@YOUR_CI_CD_IP

# Test Production Linode (IP address only)
ssh root@YOUR_PRODUCTION_IP

Expected output: SSH login prompt asking for root password.

If something goes wrong:

  • Verify the IP addresses are correct
  • Check that SSH is enabled on the Linodes
  • Ensure your local machine can reach the Linodes (no firewall blocking)

0.3 Choose Your Names

Before proceeding, decide on:

  1. Service Account Name: Choose a username for the service account (e.g., appuser, deploy, service)

    • Replace SERVICE_USER in this guide with your chosen name
    • This account runs the actual application services
  2. Deployment User Name: Choose a username for deployment tasks (e.g., deploy, ci, admin)

    • Replace DEPLOY_USER in this guide with your chosen name
    • This account has sudo privileges for deployment tasks
  3. Application Name: Choose a name for your application (e.g., myapp, webapp, api)

    • Replace APP_NAME in this guide with your chosen name
  4. Domain Name (Optional): If you have a domain, note it for SSL configuration

    • Replace your-domain.com in this guide with your actual domain

Example:

  • If you choose appuser as service account, deploy as deployment user, and myapp as application name:
    • Replace all SERVICE_USER with appuser
    • Replace all DEPLOY_USER with deploy
    • Replace all APP_NAME with myapp
    • If you have a domain example.com, replace your-domain.com with example.com

Security Model:

  • Service Account (SERVICE_USER): Runs application services, no sudo access
  • Deployment User (DEPLOY_USER): Handles deployments via SSH, has sudo access
  • Root: Only used for initial setup, then disabled for SSH access

0.4 Set Up SSH Key Authentication for Local Development

Important: This step should be done on both Linodes to enable secure SSH access from your local development machine.

0.4.1 Generate SSH Key on Your Local Machine

On your local development machine, generate an SSH key pair:

# Generate SSH key pair (if you don't already have one)
ssh-keygen -t ed25519 -C "your-email@example.com" -f ~/.ssh/id_ed25519 -N ""

# Or use existing key if you have one
ls ~/.ssh/id_ed25519.pub
0.4.2 Add Your Public Key to Both Linodes

Copy your public key to both Linodes:

# Copy your public key to CI/CD Linode
ssh-copy-id root@YOUR_CI_CD_IP

# Copy your public key to Production Linode
ssh-copy-id root@YOUR_PRODUCTION_IP

Alternative method (if ssh-copy-id doesn't work):

# Copy your public key content
cat ~/.ssh/id_ed25519.pub

# Then manually add to each server
ssh root@YOUR_CI_CD_IP
echo "YOUR_PUBLIC_KEY_CONTENT" >> ~/.ssh/authorized_keys

ssh root@YOUR_PRODUCTION_IP
echo "YOUR_PUBLIC_KEY_CONTENT" >> ~/.ssh/authorized_keys
0.4.3 Test SSH Key Authentication

Test that you can access both servers without passwords:

# Test CI/CD Linode
ssh root@YOUR_CI_CD_IP 'echo "SSH key authentication works for CI/CD"'

# Test Production Linode
ssh root@YOUR_PRODUCTION_IP 'echo "SSH key authentication works for Production"'

Expected output: The echo messages should appear without password prompts.

0.4.4 Create Deployment Users

On both Linodes, create the deployment user with sudo privileges:

# Create deployment user
sudo useradd -m -s /bin/bash DEPLOY_USER
sudo usermod -aG sudo DEPLOY_USER

# Set a secure password (you won't need it for SSH key auth, but it's good practice)
echo "DEPLOY_USER:$(openssl rand -base64 32)" | sudo chpasswd

# Copy your SSH key to the deployment user
sudo mkdir -p /home/DEPLOY_USER/.ssh
sudo cp ~/.ssh/authorized_keys /home/DEPLOY_USER/.ssh/
sudo chown -R DEPLOY_USER:DEPLOY_USER /home/DEPLOY_USER/.ssh
sudo chmod 700 /home/DEPLOY_USER/.ssh
sudo chmod 600 /home/DEPLOY_USER/.ssh/authorized_keys
0.4.5 Disable Root SSH Access

On both Linodes, disable root SSH access for security:

# Edit SSH configuration
sudo nano /etc/ssh/sshd_config

Find and modify these lines:

PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes

Note: We disable root SSH access entirely and use the deployment user for all SSH operations.

Restart SSH service:

sudo systemctl restart ssh

Important: Test SSH access with the deployment user before closing your current session to ensure you don't get locked out.

0.4.6 Test Deployment User Access

Test that you can access both servers as the deployment user:

# Test CI/CD Linode
ssh DEPLOY_USER@YOUR_CI_CD_IP 'echo "Deployment user SSH access works for CI/CD"'

# Test Production Linode
ssh DEPLOY_USER@YOUR_PRODUCTION_IP 'echo "Deployment user SSH access works for Production"'

Expected output: The echo messages should appear without password prompts.

0.4.7 Create SSH Config for Easy Access

On your local machine, create an SSH config file for easy access:

# Create SSH config
cat > ~/.ssh/config << 'EOF'
Host ci-cd-dev
    HostName YOUR_CI_CD_IP
    User DEPLOY_USER
    IdentityFile ~/.ssh/id_ed25519
    StrictHostKeyChecking no

Host production-dev
    HostName YOUR_PRODUCTION_IP
    User DEPLOY_USER
    IdentityFile ~/.ssh/id_ed25519
    StrictHostKeyChecking no
EOF

chmod 600 ~/.ssh/config

Now you can access servers easily:

ssh ci-cd-dev
ssh production-dev

Part 1: CI/CD Linode Setup

Step 1: Initial System Setup

1.1 Update the System

sudo apt update && sudo apt upgrade -y

What this does: Updates package lists and upgrades all installed packages to their latest versions.

Expected output: A list of packages being updated, followed by completion messages.

1.2 Configure Timezone

# Configure timezone interactively
sudo dpkg-reconfigure tzdata

# Verify timezone setting
date

What this does: Opens an interactive dialog to select your timezone. Navigate through the menus to choose your preferred timezone (e.g., UTC, America/New_York, Europe/London, Asia/Tokyo).

Expected output: After selecting your timezone, the date command should show the current date and time in your selected timezone.

1.3 Configure /etc/hosts

# Add localhost entries for both IPv4 and IPv6
echo "127.0.0.1 localhost" | sudo tee -a /etc/hosts
echo "::1 localhost ip6-localhost ip6-loopback" | sudo tee -a /etc/hosts
echo "YOUR_CI_CD_IPV4_ADDRESS localhost" | sudo tee -a /etc/hosts
echo "YOUR_CI_CD_IPV6_ADDRESS localhost" | sudo tee -a /etc/hosts

# Verify the configuration
cat /etc/hosts

What this does:

  • Adds localhost entries for both IPv4 and IPv6 addresses to /etc/hosts
  • Ensures proper localhost resolution for both IPv4 and IPv6

Important: Replace YOUR_CI_CD_IPV4_ADDRESS and YOUR_CI_CD_IPV6_ADDRESS with the actual IPv4 and IPv6 addresses of your CI/CD Linode obtained from your Linode dashboard.

Expected output: The /etc/hosts file should show entries for 127.0.0.1, ::1, and your Linode's actual IP addresses all mapping to localhost.

1.4 Install Essential Packages

sudo apt install -y \
    curl \
    wget \
    git \
    build-essential \
    pkg-config \
    libssl-dev \
    ca-certificates \
    apt-transport-https \
    software-properties-common \
    apache2-utils

What this does: Installs development tools, SSL libraries, and utilities needed for Docker and application building.

Step 2: Create Users

2.1 Create Service Account

sudo useradd -r -s /bin/bash -m -d /home/SERVICE_USER SERVICE_USER
sudo usermod -aG sudo SERVICE_USER
echo "SERVICE_USER:$(openssl rand -base64 32)" | sudo chpasswd

2.2 Verify Users

sudo su - SERVICE_USER
whoami
pwd
exit

sudo su - DEPLOY_USER
whoami
pwd
exit

Step 3: Install Docker

3.1 Add Docker Repository

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update

3.2 Install Docker Packages

sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

3.3 Configure Docker for Service Account

sudo usermod -aG docker SERVICE_USER

Step 4: Set Up Docker Registry

4.1 Create Registry Directory

sudo mkdir -p /opt/registry
sudo chown SERVICE_USER:SERVICE_USER /opt/registry

4.2 Create Registry Configuration

cat > /opt/registry/config.yml << 'EOF'
version: 0.1
log:
  level: info
storage:
  filesystem:
    rootdirectory: /var/lib/registry
  delete:
    enabled: true
  cache:
    blobdescriptor: inmemory
http:
  addr: :5000
  headers:
    X-Content-Type-Options: [nosniff]
    X-Frame-Options: [DENY]
    X-XSS-Protection: [1; mode=block]
  # Enable public read access
  secret: "your-secret-key-here"
  # Restrict write access to specific IPs
  auth:
    htpasswd:
      realm: basic-realm
      path: /etc/docker/registry/auth.htpasswd
health:
  storagedriver:
    enabled: true
    interval: 10s
    threshold: 3
EOF

4.3 Create Authentication File

# Create htpasswd file for authentication
mkdir -p /opt/registry/auth
htpasswd -Bbn push-user "$(openssl rand -base64 32)" > /opt/registry/auth.htpasswd

# Create a read-only user (optional, for additional security)
htpasswd -Bbn read-user "$(openssl rand -base64 32)" >> /opt/registry/auth.htpasswd

4.4 Create Docker Compose for Registry

cat > /opt/registry/docker-compose.yml << 'EOF'
version: '3.8'

services:
  registry:
    image: registry:2
    ports:
      - "5000:5000"
    volumes:
      - ./config.yml:/etc/docker/registry/config.yml:ro
      - ./auth.htpasswd:/etc/docker/registry/auth.htpasswd:ro
      - registry_data:/var/lib/registry
    restart: unless-stopped
    networks:
      - registry_network

  registry-ui:
    image: joxit/docker-registry-ui:latest
    ports:
      - "8080:80"
    environment:
      - REGISTRY_TITLE=APP_NAME Registry
      - REGISTRY_URL=http://registry:5000
    depends_on:
      - registry
    restart: unless-stopped
    networks:
      - registry_network

volumes:
  registry_data:

networks:
  registry_network:
    driver: bridge
EOF

4.5 Install Required Tools

# Install htpasswd utility
sudo apt install -y apache2-utils

4.6 Start Registry

cd /opt/registry
docker-compose up -d

4.7 Test Registry Setup

# Check if containers are running
cd /opt/registry
docker-compose ps

# Test registry API
curl http://localhost:5000/v2/_catalog

# Test registry UI (optional)
curl -I http://localhost:8080

# Test Docker push/pull (optional but recommended)
# Create a test image
echo "FROM alpine:latest" > /tmp/test.Dockerfile
echo "RUN echo 'Hello from test image'" >> /tmp/test.Dockerfile

# Build and tag test image
docker build -f /tmp/test.Dockerfile -t localhost:5000/test:latest /tmp

# Push to registry
docker push localhost:5000/test:latest

# Verify image is in registry
curl http://localhost:5000/v2/_catalog
curl http://localhost:5000/v2/test/tags/list

# Pull image back (verifies pull works)
docker rmi localhost:5000/test:latest
docker pull localhost:5000/test:latest

# Clean up test image
docker rmi localhost:5000/test:latest
rm /tmp/test.Dockerfile

Expected Output:

  • docker-compose ps should show both registry and registry-ui as "Up"
  • curl http://localhost:5000/v2/_catalog should return {"repositories":[]} (empty initially)
  • curl -I http://localhost:8080 should return HTTP 200
  • Push/pull test should complete successfully

If something goes wrong:

  • Check container logs: docker-compose logs
  • Verify ports are open: netstat -tlnp | grep :5000
  • Check Docker daemon config: cat /etc/docker/daemon.json
  • Restart registry: docker-compose restart

Step 5: Configure Docker for Registry Access

5.1 Configure Docker for Registry Access

# Get the push user credentials
PUSH_USER="push-user"
PUSH_PASSWORD=$(grep push-user /opt/registry/auth.htpasswd | cut -d: -f2)

sudo tee /etc/docker/daemon.json << EOF
{
  "insecure-registries": ["YOUR_CI_CD_IP:5000"],
  "registry-mirrors": [],
  "auths": {
    "YOUR_CI_CD_IP:5000": {
      "auth": "$(echo -n "${PUSH_USER}:${PUSH_PASSWORD}" | base64)"
    }
  }
}
EOF

5.2 Restart Docker

sudo systemctl restart docker

Public Registry Access Model

Your registry is now configured with the following access model:

Public Read Access

Anyone can pull images without authentication:

# From any machine (public access)
docker pull YOUR_CI_CD_IP:5000/APP_NAME/backend:latest
docker pull YOUR_CI_CD_IP:5000/APP_NAME/frontend:latest

Authenticated Write Access

Only the CI/CD Linode can push images (using credentials):

# From CI/CD Linode only (authenticated)
docker push YOUR_CI_CD_IP:5000/APP_NAME/backend:latest
docker push YOUR_CI_CD_IP:5000/APP_NAME/frontend:latest

Registry UI Access

Public web interface for browsing images:

http://YOUR_CI_CD_IP:8080

Client Configuration

For other machines to pull images, they only need:

# Add to /etc/docker/daemon.json on client machines
{
  "insecure-registries": ["YOUR_CI_CD_IP:5000"]
}
# No authentication needed for pulls

Step 6: Set Up SSH for Production Communication

6.1 Generate SSH Key Pair

ssh-keygen -t ed25519 -C "ci-cd-server" -f ~/.ssh/id_ed25519 -N ""

6.2 Create SSH Config

cat > ~/.ssh/config << 'EOF'
Host production
    HostName YOUR_PRODUCTION_IP
    User DEPLOY_USER
    IdentityFile ~/.ssh/id_ed25519
    StrictHostKeyChecking no
    UserKnownHostsFile /dev/null
EOF

chmod 600 ~/.ssh/config

Step 7: Install Forgejo Actions Runner

7.1 Download Runner

cd ~
wget https://code.forgejo.org/forgejo/runner/releases/download/v0.2.11/forgejo-runner-0.2.11-linux-amd64
chmod +x forgejo-runner-0.2.11-linux-amd64
sudo mv forgejo-runner-0.2.11-linux-amd64 /usr/local/bin/forgejo-runner

7.2 Create Systemd Service

sudo tee /etc/systemd/system/forgejo-runner.service > /dev/null << 'EOF'
[Unit]
Description=Forgejo Actions Runner
After=network.target

[Service]
Type=simple
User=SERVICE_USER
WorkingDirectory=/home/SERVICE_USER
ExecStart=/usr/local/bin/forgejo-runner daemon
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
EOF

7.3 Enable Service

sudo systemctl daemon-reload
sudo systemctl enable forgejo-runner.service

7.4 Test Runner Configuration

# Check if the runner is running
sudo systemctl status forgejo-runner.service

# Check runner logs
sudo journalctl -u forgejo-runner.service -f --no-pager

# Test runner connectivity (in a separate terminal)
forgejo-runner list

# Verify runner appears in Forgejo
# Go to your Forgejo repository → Settings → Actions → Runners
# You should see your runner listed as "ci-cd-runner" with status "Online"

Expected Output:

  • systemctl status should show "active (running)"
  • forgejo-runner list should show your runner
  • Forgejo web interface should show the runner as online

If something goes wrong:

  • Check logs: sudo journalctl -u forgejo-runner.service -f
  • Verify token: Make sure the registration token is correct
  • Check network: Ensure the runner can reach your Forgejo instance
  • Restart service: sudo systemctl restart forgejo-runner.service

Step 8: Set Up Monitoring and Cleanup

8.1 Monitoring Script

Important: The repository includes a pre-configured monitoring script in the scripts/ directory that can be used for both CI/CD and production monitoring.

Repository Script:

  • scripts/monitor.sh - Comprehensive monitoring script with support for both CI/CD and production environments

To use the repository monitoring script:

# Clone the repository if not already done
git clone https://your-forgejo-instance/your-username/APP_NAME.git /tmp/monitoring-setup
cd /tmp/monitoring-setup

# Make the script executable
chmod +x scripts/monitor.sh

# Test CI/CD monitoring
./scripts/monitor.sh --type ci-cd

# Test production monitoring (if you have a production setup)
./scripts/monitor.sh --type production

# Clean up
cd /
rm -rf /tmp/monitoring-setup

Alternative: Create a local copy for convenience:

# Copy the script to your home directory for easy access
cp /tmp/monitoring-setup/scripts/monitor.sh ~/monitor.sh
chmod +x ~/monitor.sh

# Test the local copy
~/monitor.sh --type ci-cd

Note: The repository script is more comprehensive and includes proper error handling, colored output, and support for both CI/CD and production environments. It automatically detects the environment and provides appropriate monitoring information.

8.2 Cleanup Script

Important: The repository includes a pre-configured cleanup script in the scripts/ directory that can be used for both CI/CD and production cleanup operations.

Repository Script:

  • scripts/cleanup.sh - Comprehensive cleanup script with support for both CI/CD and production environments

To use the repository cleanup script:

# Clone the repository if not already done
git clone https://your-forgejo-instance/your-username/APP_NAME.git /tmp/cleanup-setup
cd /tmp/cleanup-setup

# Make the script executable
chmod +x scripts/cleanup.sh

# Test CI/CD cleanup (dry run first)
./scripts/cleanup.sh --type ci-cd --dry-run

# Run CI/CD cleanup
./scripts/cleanup.sh --type ci-cd

# Test production cleanup (dry run first)
./scripts/cleanup.sh --type production --dry-run

# Clean up
cd /
rm -rf /tmp/cleanup-setup

Alternative: Create a local copy for convenience:

# Copy the script to your home directory for easy access
cp /tmp/cleanup-setup/scripts/cleanup.sh ~/cleanup.sh
chmod +x ~/cleanup.sh

# Test the local copy (dry run)
~/cleanup.sh --type ci-cd --dry-run

Note: The repository script is more comprehensive and includes proper error handling, colored output, dry-run mode, and support for both CI/CD and production environments. It automatically detects the environment and provides appropriate cleanup operations.

8.3 Test Cleanup Script

# Create some test images to clean up
docker pull alpine:latest
docker pull nginx:latest
docker tag alpine:latest test-cleanup:latest
docker tag nginx:latest test-cleanup2:latest

# Test cleanup with dry run first
./scripts/cleanup.sh --type ci-cd --dry-run

# Run the cleanup script
./scripts/cleanup.sh --type ci-cd

# Verify cleanup worked
echo "Checking remaining images:"
docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"

echo "Checking remaining volumes:"
docker volume ls

echo "Checking remaining networks:"
docker network ls

Expected Output:

  • Cleanup script should run without errors
  • Test images should be removed
  • System should report cleanup completion
  • Remaining images should be minimal (only actively used ones)

If something goes wrong:

  • Check script permissions: ls -la scripts/cleanup.sh
  • Verify Docker access: docker ps
  • Check registry access: cd /opt/registry && docker-compose ps
  • Run manually: bash -x scripts/cleanup.sh

8.4 Set Up Automated Cleanup

# Create a cron job to run cleanup daily at 3 AM using the repository script
(crontab -l 2>/dev/null; echo "0 3 * * * cd /tmp/cleanup-setup && ./scripts/cleanup.sh --type ci-cd >> /tmp/cleanup.log 2>&1") | crontab -

# Verify the cron job was added
crontab -l

What this does:

  • Runs automatically: The cleanup script runs every day at 3:00 AM
  • Frequency: Daily cleanup to prevent disk space issues
  • Logging: All cleanup output is logged to /tmp/cleanup.log
  • What it cleans: Unused Docker images, volumes, networks, and registry images

Alternative: Use a local copy for automated cleanup:

# If you created a local copy, use that instead
(crontab -l 2>/dev/null; echo "0 3 * * * ~/cleanup.sh --type ci-cd >> ~/cleanup.log 2>&1") | crontab -

Step 9: Configure Firewall

sudo ufw --force enable
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 5000/tcp  # Docker registry (public read access)
sudo ufw allow 8080/tcp  # Registry UI (public read access)

Security Model:

  • Port 5000 (Registry): Public read access, authenticated write access
  • Port 8080 (UI): Public read access for browsing images
  • SSH: Restricted to your IP addresses
  • All other ports: Blocked

Step 10: Test CI/CD Setup

10.1 Test Docker Installation

docker --version
docker-compose --version

10.2 Check Registry Status

cd /opt/registry
docker-compose ps

10.3 Test Registry Access

curl http://localhost:5000/v2/_catalog

10.4 Get Public Key for Production Server

cat ~/.ssh/id_ed25519.pub

Important: Copy this public key - you'll need it for the production server setup.

Step 11: Configure Forgejo Actions Runner

11.1 Get Runner Token

  1. Go to your Forgejo repository
  2. Navigate to Settings → Actions → Runners
  3. Click "New runner"
  4. Copy the registration token

11.2 Configure Runner

# Get the registration token from your Forgejo repository
# Go to Settings → Actions → Runners → New runner
# Copy the registration token

# Configure the runner
forgejo-runner register \
  --instance https://your-forgejo-instance \
  --token YOUR_TOKEN \
  --name "ci-cd-runner" \
  --labels "ubuntu-latest,docker" \
  --no-interactive

11.3 Start Runner

sudo systemctl start forgejo-runner.service
sudo systemctl status forgejo-runner.service

11.4 Test Runner Configuration

# Check if the runner is running
sudo systemctl status forgejo-runner.service

# Check runner logs
sudo journalctl -u forgejo-runner.service -f --no-pager

# Test runner connectivity (in a separate terminal)
forgejo-runner list

# Verify runner appears in Forgejo
# Go to your Forgejo repository → Settings → Actions → Runners
# You should see your runner listed as "ci-cd-runner" with status "Online"

Expected Output:

  • systemctl status should show "active (running)"
  • forgejo-runner list should show your runner
  • Forgejo web interface should show the runner as online

If something goes wrong:

  • Check logs: sudo journalctl -u forgejo-runner.service -f
  • Verify token: Make sure the registration token is correct
  • Check network: Ensure the runner can reach your Forgejo instance
  • Restart service: sudo systemctl restart forgejo-runner.service

Part 2: Production Linode Setup

Step 12: Initial System Setup

12.1 Update the System

sudo apt update && sudo apt upgrade -y

12.2 Configure Timezone

# Configure timezone interactively
sudo dpkg-reconfigure tzdata

# Verify timezone setting
date

What this does: Opens an interactive dialog to select your timezone. Navigate through the menus to choose your preferred timezone (e.g., UTC, America/New_York, Europe/London, Asia/Tokyo).

Expected output: After selecting your timezone, the date command should show the current date and time in your selected timezone.

12.3 Configure /etc/hosts

# Add localhost entries for both IPv4 and IPv6
echo "127.0.0.1 localhost" | sudo tee -a /etc/hosts
echo "::1 localhost ip6-localhost ip6-loopback" | sudo tee -a /etc/hosts
echo "YOUR_PRODUCTION_IPV4_ADDRESS localhost" | sudo tee -a /etc/hosts
echo "YOUR_PRODUCTION_IPV6_ADDRESS localhost" | sudo tee -a /etc/hosts

# Verify the configuration
cat /etc/hosts

What this does:

  • Adds localhost entries for both IPv4 and IPv6 addresses to /etc/hosts
  • Ensures proper localhost resolution for both IPv4 and IPv6

Important: Replace YOUR_PRODUCTION_IPV4_ADDRESS and YOUR_PRODUCTION_IPV6_ADDRESS with the actual IPv4 and IPv6 addresses of your Production Linode obtained from your Linode dashboard.

Expected output: The /etc/hosts file should show entries for 127.0.0.1, ::1, and your Linode's actual IP addresses all mapping to localhost.

12.4 Install Essential Packages

sudo apt install -y \
    curl \
    wget \
    git \
    ca-certificates \
    apt-transport-https \
    software-properties-common \
    ufw \
    fail2ban \
    htop \
    nginx \
    certbot \
    python3-certbot-nginx

Step 13: Create Users

13.1 Create the SERVICE_USER User

sudo useradd -r -s /bin/bash -m -d /home/SERVICE_USER SERVICE_USER
sudo usermod -aG sudo SERVICE_USER
echo "SERVICE_USER:$(openssl rand -base64 32)" | sudo chpasswd

13.2 Verify Users

sudo su - SERVICE_USER
whoami
pwd
exit

sudo su - DEPLOY_USER
whoami
pwd
exit

Step 14: Install Docker

14.1 Add Docker Repository

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update

14.2 Install Docker Packages

sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

14.3 Configure Docker for Service Account

sudo usermod -aG docker SERVICE_USER

Step 15: Install Docker Compose

sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

Step 16: Configure Security

16.1 Configure Firewall

sudo ufw --force enable
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 3000/tcp
sudo ufw allow 3001/tcp

16.2 Configure Fail2ban

sudo systemctl enable fail2ban
sudo systemctl start fail2ban

Step 17: Create Application Directory

17.1 Create Directory Structure

sudo mkdir -p /opt/APP_NAME
sudo chown SERVICE_USER:SERVICE_USER /opt/APP_NAME

Note: Replace APP_NAME with your actual application name. This directory name can be controlled via the APP_NAME secret in your Forgejo repository settings. If you set the APP_NAME secret to myapp, the deployment directory will be /opt/myapp.

17.2 Create SSL Directory (Optional - for domain users)

sudo mkdir -p /opt/APP_NAME/nginx/ssl
sudo chown SERVICE_USER:SERVICE_USER /opt/APP_NAME/nginx/ssl

Step 18: Clone Repository and Set Up Application Files

18.1 Switch to SERVICE_USER User

sudo su - SERVICE_USER

18.2 Clone Repository

cd /opt/APP_NAME
git clone https://your-forgejo-instance/your-username/APP_NAME.git .

Important: The repository includes a pre-configured nginx/nginx.conf file that handles both SSL and non-SSL scenarios, with proper security headers, rate limiting, and CORS configuration. This file will be automatically used by the Docker Compose setup.

Important: The repository also includes a pre-configured .forgejo/workflows/ci.yml file that handles the complete CI/CD pipeline including testing, building, and deployment. This workflow is already set up to work with the private registry and production deployment.

Note: Replace your-forgejo-instance and your-username/APP_NAME with your actual Forgejo instance URL and repository path.

18.3 Create Environment File

The repository doesn't include a .env.example file for security reasons. The CI/CD pipeline will create the .env file dynamically during deployment. However, for manual testing or initial setup, you can create a basic .env file:

cat > /opt/APP_NAME/.env << 'EOF'
# Production Environment Variables
POSTGRES_PASSWORD=your_secure_password_here
REGISTRY=YOUR_CI_CD_IP:5000
IMAGE_NAME=APP_NAME
IMAGE_TAG=latest

# Database Configuration
POSTGRES_DB=sharenet
POSTGRES_USER=sharenet
DATABASE_URL=postgresql://sharenet:your_secure_password_here@postgres:5432/sharenet

# Application Configuration
NODE_ENV=production
RUST_LOG=info
RUST_BACKTRACE=1
EOF

Important: Replace YOUR_CI_CD_IP with your actual CI/CD Linode IP address.

Default Environment Variables (from docker-compose.yml):

  • POSTGRES_DB=sharenet - PostgreSQL database name
  • POSTGRES_USER=sharenet - PostgreSQL username
  • POSTGRES_PASSWORD=changeme - PostgreSQL password (should be changed)
  • REGISTRY=your-username/sharenet - Docker registry path (used as fallback)
  • IMAGE_NAME=your-username/sharenet - Docker image name (used as fallback)
  • IMAGE_TAG=latest - Docker image tag (used as fallback)

Note: The database user and database name can be controlled via the POSTGRES_USER and POSTGRES_DB secrets in your Forgejo repository settings. If you set these secrets, they will override the default values used in this environment file.

Security Note: Always change the default POSTGRES_PASSWORD from changeme to a strong, unique password in production.

18.4 Verify Repository Contents

# Check that the docker-compose.yml file is present
ls -la docker-compose.yml

# Check that the nginx configuration is present
ls -la nginx/nginx.conf

# Check that the CI/CD workflow is present
ls -la .forgejo/workflows/ci.yml

# Check the repository structure
ls -la

# Verify the docker-compose.yml content
head -20 docker-compose.yml

# Verify the nginx configuration content
head -20 nginx/nginx.conf

# Verify the CI/CD workflow content
head -20 .forgejo/workflows/ci.yml

Expected output: You should see the docker-compose.yml file, nginx/nginx.conf file, .forgejo/workflows/ci.yml file, and other project files from your repository.

18.5 Deployment Scripts

Important: The repository includes pre-configured deployment scripts in the scripts/ directory that are used by the CI/CD pipeline. These scripts handle safe production deployments with database migrations, backups, and rollback capabilities.

Repository Scripts (used by CI/CD pipeline):

  • scripts/deploy.sh - Main deployment script with migration support
  • scripts/deploy-local.sh - Local development deployment script
  • scripts/migrate.sh - Database migration management
  • scripts/validate_migrations.sh - Migration validation
  • scripts/monitor.sh - Comprehensive monitoring script for both CI/CD and production environments
  • scripts/cleanup.sh - Comprehensive cleanup script for both CI/CD and production environments
  • scripts/backup.sh - Comprehensive backup script for both CI/CD and production environments

To use the repository deployment scripts:

# The scripts are already available in the cloned repository
cd /opt/APP_NAME

# Make the scripts executable
chmod +x scripts/deploy.sh scripts/deploy-local.sh

# Test local deployment
./scripts/deploy-local.sh status

# Run local deployment
./scripts/deploy-local.sh deploy

# Test production deployment (dry run)
./scripts/deploy.sh check

# Run production deployment
./scripts/deploy.sh deploy

Alternative: Create a local copy for convenience:

# Copy the local deployment script to the application directory for easy access
cp scripts/deploy-local.sh /opt/APP_NAME/deploy-local.sh
chmod +x /opt/APP_NAME/deploy-local.sh

# Test the local copy
cd /opt/APP_NAME
./deploy-local.sh status

Note: The repository scripts are more comprehensive and include proper error handling, colored output, and multiple commands. The scripts/deploy.sh is used by the CI/CD pipeline and includes database migration handling, backup creation, and rollback capabilities. The scripts/deploy-local.sh is designed for local development deployments and includes status checking, restart, and log viewing capabilities.

18.6 Backup Script

Important: The repository includes a pre-configured backup script in the scripts/ directory that can be used for both CI/CD and production backup operations.

Repository Script:

  • scripts/backup.sh - Comprehensive backup script with support for both CI/CD and production environments

To use the repository backup script:

# The script is already available in the cloned repository
cd /opt/APP_NAME

# Make the script executable
chmod +x scripts/backup.sh

# Test production backup (dry run first)
./scripts/backup.sh --type production --app-name APP_NAME --dry-run

# Run production backup
./scripts/backup.sh --type production --app-name APP_NAME

# Test CI/CD backup (dry run first)
./scripts/backup.sh --type ci-cd --app-name APP_NAME --dry-run

Alternative: Create a local copy for convenience:

# Copy the script to the application directory for easy access
cp scripts/backup.sh /opt/APP_NAME/backup-local.sh
chmod +x /opt/APP_NAME/backup-local.sh

# Test the local copy (dry run)
cd /opt/APP_NAME
./backup-local.sh --type production --app-name APP_NAME --dry-run

Note: The repository script is more comprehensive and includes proper error handling, colored output, dry-run mode, and support for both CI/CD and production environments. It automatically detects the environment and provides appropriate backup operations.

18.6.1 Set Up Automated Backup Scheduling

# Create a cron job to run backups daily at 2 AM using the repository script
(crontab -l 2>/dev/null; echo "0 2 * * * cd /opt/APP_NAME && ./scripts/backup.sh --type production --app-name APP_NAME >> /opt/APP_NAME/backup.log 2>&1") | crontab -

# Verify the cron job was added
crontab -l

What this does:

  • Runs automatically: The backup script runs every day at 2:00 AM
  • Frequency: Daily backups to ensure minimal data loss
  • Logging: All backup output is logged to /opt/APP_NAME/backup.log
  • Retention: The script automatically keeps only the last 7 days of backups (configurable)

To test the backup manually:

cd /opt/APP_NAME
./scripts/backup.sh --type production --app-name APP_NAME

To view backup logs:

tail -f /opt/APP_NAME/backup.log

Alternative: Use a local copy for automated backup:

# If you created a local copy, use that instead
(crontab -l 2>/dev/null; echo "0 2 * * * cd /opt/APP_NAME && ./backup-local.sh --type production --app-name APP_NAME >> /opt/APP_NAME/backup.log 2>&1") | crontab -

18.7 Monitoring Script

Important: The repository includes a pre-configured monitoring script in the scripts/ directory that can be used for production monitoring.

Repository Script:

  • scripts/monitor.sh - Comprehensive monitoring script with support for both CI/CD and production environments

To use the repository monitoring script:

# The script is already available in the cloned repository
cd /opt/APP_NAME

# Make the script executable
chmod +x scripts/monitor.sh

# Test production monitoring
./scripts/monitor.sh --type production --app-name APP_NAME

Alternative: Create a local copy for convenience:

# Copy the script to the application directory for easy access
cp scripts/monitor.sh /opt/APP_NAME/monitor-local.sh
chmod +x /opt/APP_NAME/monitor-local.sh

# Test the local copy
cd /opt/APP_NAME
./monitor-local.sh --type production --app-name APP_NAME

Note: The repository script is more comprehensive and includes proper error handling, colored output, health checks, and automatic environment detection. It provides better monitoring information than a simple local script.

18.7.1 Set Up Automated Monitoring

# Create a cron job to run monitoring every 5 minutes using the repository script
(crontab -l 2>/dev/null; echo "*/5 * * * * cd /opt/APP_NAME && ./scripts/monitor.sh --type production --app-name APP_NAME >> /opt/APP_NAME/monitor.log 2>&1") | crontab -

# Verify the cron job was added
crontab -l

What this does:

  • Runs automatically: The monitoring script runs every 5 minutes
  • Frequency: Every 5 minutes to catch issues quickly
  • Logging: All monitoring output is logged to /opt/APP_NAME/monitor.log
  • What it monitors: Container status, recent logs, CPU/memory/disk usage, network connections, health checks

To test the monitoring manually:

cd /opt/APP_NAME
./scripts/monitor.sh --type production --app-name APP_NAME

To view monitoring logs:

tail -f /opt/APP_NAME/monitor.log

Step 19: Set Up SSH for CI/CD Communication

19.1 Generate SSH Key Pair

ssh-keygen -t ed25519 -C "production-server" -f ~/.ssh/id_ed25519 -N ""

19.2 Create SSH Config

cat > ~/.ssh/config << 'EOF'
Host ci-cd
    HostName YOUR_CI_CD_IP
    User SERVICE_USER
    IdentityFile ~/.ssh/id_ed25519
    StrictHostKeyChecking no
    UserKnownHostsFile /dev/null
EOF

chmod 600 ~/.ssh/config

Step 20: Exchange SSH Keys

20.1 Get Your Public Key

cat ~/.ssh/id_ed25519.pub

Important: Copy this public key - you'll need it for the CI/CD server setup.

20.2 Add CI/CD Server's Public Key

echo "CI_CD_PUBLIC_KEY_HERE" >> ~/.ssh/authorized_keys
sed -i 's/YOUR_CI_CD_IP/YOUR_ACTUAL_CI_CD_IP/g' ~/.ssh/config

Note: Replace CI_CD_PUBLIC_KEY_HERE with the actual public key from your CI/CD server.

Step 21: Update Application Configuration for CI/CD

21.1 Update Environment Variables

cd /opt/APP_NAME
nano .env

Required changes:

  • Replace YOUR_CI_CD_IP with your actual CI/CD Linode IP address
  • Replace your_secure_password_here with a strong database password
  • Update DATABASE_URL with the same password

Step 22: Configure SSL Certificates (Optional - Domain Users Only)

Skip this step if you don't have a domain name.

22.1 Install SSL Certificates

sudo certbot --nginx -d your-domain.com

22.2 Copy SSL Certificates

sudo cp /etc/letsencrypt/live/your-domain.com/fullchain.pem /opt/APP_NAME/nginx/ssl/
sudo cp /etc/letsencrypt/live/your-domain.com/privkey.pem /opt/APP_NAME/nginx/ssl/
sudo chown SERVICE_USER:SERVICE_USER /opt/APP_NAME/nginx/ssl/*

Step 23: Test CI/CD Integration

23.1 Test SSH Connection

ssh ci-cd 'echo Connection successful'

Expected output: Connection successful.

23.2 Test Registry Connection

curl http://YOUR_ACTUAL_CI_IP:5000/v2/_catalog

Expected output: {"repositories":[]} or a list of available images.


Part 3: Pipeline Configuration

Step 24: Configure Forgejo Repository Secrets

Go to your Forgejo repository → Settings → Secrets and Variables → Actions, and add the following secrets:

Required Secrets:

  • CI_HOST: Your CI/CD Linode IP address

    • Purpose: Used by the workflow to connect to your private Docker registry
    • Example: 192.168.1.100
  • PROD_HOST: Your Production Linode IP address

    • Purpose: Used by the deployment job to SSH into your production server
    • Example: 192.168.1.101
  • PROD_USER: SSH username for production server

    • Purpose: Username for SSH connection to production server
    • Value: Should be DEPLOY_USER (the deployment user you created)
    • Example: deploy
  • PROD_SSH_KEY: SSH private key for deployment

    • Purpose: Private key for SSH authentication to production server
    • Source: Copy the private key from your CI/CD server
    • How to get: On CI/CD server, run cat ~/.ssh/id_ed25519
    • Format: Include the entire key including -----BEGIN OPENSSH PRIVATE KEY----- and -----END OPENSSH PRIVATE KEY-----

Optional Secrets (for enhanced security and flexibility):

  • APP_NAME: Application name (used for directory, database, and image names)

    • Purpose: Controls the application directory name and database names
    • Default: sharenet (if not set)
    • Example: myapp, webapp, api
    • Note: This affects the deployment directory /opt/APP_NAME and database names
  • POSTGRES_USER: PostgreSQL username for the application database

    • Purpose: Username for the application's PostgreSQL database
    • Default: sharenet (if not set)
    • Example: appuser, webuser, apiuser
    • Note: Should match the user created in the PostgreSQL setup
  • POSTGRES_DB: PostgreSQL database name for the application

    • Purpose: Name of the application's PostgreSQL database
    • Default: sharenet (if not set)
    • Example: myapp, webapp, api
    • Note: Should match the database created in the PostgreSQL setup
  • POSTGRES_PASSWORD: Database password for production

    • Purpose: Secure database password for production environment
    • Note: If not set, the workflow will use a default password
  • REGISTRY_USERNAME: Username for Docker registry (if using authentication)

    • Purpose: Username for private registry access
    • Note: Only needed if your registry requires authentication
  • REGISTRY_PASSWORD: Password for Docker registry (if using authentication)

    • Purpose: Password for private registry access
    • Note: Only needed if your registry requires authentication

How to Add Secrets:

  1. Go to your Forgejo repository
  2. Navigate to SettingsSecrets and VariablesActions
  3. Click New Secret
  4. Enter the secret name and value
  5. Click Add Secret

Security Notes:

  • Never commit secrets to your repository
  • Use strong, unique passwords for each environment
  • Rotate secrets regularly for enhanced security
  • Limit access to repository settings to trusted team members only

Step 25: Test the Complete Pipeline

25.1 Push Code Changes

Make a small change to your code and push to trigger the CI/CD pipeline:

# In your local repository
echo "# Test deployment" >> README.md
git add README.md
git commit -m "Test CI/CD pipeline"
git push

25.2 Monitor Pipeline

  1. Go to your Forgejo repository
  2. Navigate to Actions tab
  3. Monitor the workflow execution
  4. Check for any errors or issues

25.3 Verify Deployment

After successful pipeline execution:

# Check application status
cd /opt/APP_NAME
docker-compose ps

# Check application logs
docker-compose logs

# Test application access
curl -I https://your-domain.com  # or http://YOUR_PRODUCTION_IP

Testing and Verification

Step 26: Test Application Access

If you have a domain:

# Test HTTP redirect to HTTPS
curl -I http://your-domain.com

# Test HTTPS access
curl -I https://your-domain.com

# Test application health endpoint (checks backend services)
curl https://your-domain.com/health

If you don't have a domain (IP access only):

# Test HTTP access via IP
curl -I http://YOUR_PRODUCTION_IP

# Test application health endpoint (checks backend services)
curl http://YOUR_PRODUCTION_IP/health

Expected health endpoint response:

{
  "status": "healthy",
  "service": "sharenet-api",
  "timestamp": "2024-01-01T12:00:00Z"
}

Note: The /health endpoint now proxies to the backend service and returns actual service status. If the backend is not running, this endpoint will return an error, making it a true health check for the application.

Step 27: Test Monitoring

# On CI/CD server
cd /opt/registry
./scripts/monitor.sh --type ci-cd

# On Production server
cd /opt/APP_NAME
./scripts/monitor.sh --type production --app-name APP_NAME

Step 28: Test Registry Access

# Test registry API
curl http://YOUR_CI_CD_IP:5000/v2/_catalog

# Test registry UI (optional)
curl -I http://YOUR_CI_CD_IP:8080

Troubleshooting

Common Issues

  1. Docker permission denied:

    sudo usermod -aG docker SERVICE_USER
    newgrp docker
    
  2. SSL certificate issues (domain users only):

    sudo certbot certificates
    sudo certbot renew --dry-run
    
  3. Application not starting:

    cd /opt/APP_NAME
    docker-compose logs
    
  4. SSH connection failed:

    ssh -v ci-cd 'echo test'
    ssh -v production 'echo test'
    
  5. Registry connection failed:

    curl -v http://YOUR_CI_HOST_IP:5000/v2/_catalog
    
  6. Actions runner not starting:

    sudo systemctl status forgejo-runner.service
    sudo journalctl -u forgejo-runner.service -f
    

Useful Commands

  • Check system resources: htop
  • Check disk space: df -h
  • Check memory usage: free -h
  • Check network: ip addr show
  • Check firewall: sudo ufw status
  • Check logs: sudo journalctl -f

Security Best Practices

  1. Service Account: Use dedicated SERVICE_USER user with limited privileges
  2. SSH Keys: Use Ed25519 keys with proper permissions (600/700)
  3. Firewall: Configure UFW to allow only necessary ports
  4. Fail2ban: Protect against brute force attacks
  5. SSL/TLS: Use Let's Encrypt certificates with automatic renewal (domain users only)
  6. Regular Backups: Automated daily backups of database and configuration
  7. Container Isolation: Applications run in isolated Docker containers
  8. Security Headers: Nginx configured with security headers
  9. Registry Security: Use secure authentication and HTTPS for registry access

Monitoring and Maintenance

Daily Monitoring

# On CI/CD server
cd /opt/registry
./scripts/monitor.sh --type ci-cd

# On Production server
cd /opt/APP_NAME
./scripts/monitor.sh --type production --app-name APP_NAME

Weekly Maintenance

  1. Check disk space: df -h
  2. Review logs: docker-compose logs --tail=100
  3. Update system: sudo apt update && sudo apt upgrade
  4. Test backups: Verify backup files exist and are recent
    # On Production server
    cd /opt/APP_NAME
    ./scripts/backup.sh --type production --app-name APP_NAME --dry-run
    
    # Check backup directory
    ls -la backups/
    

Monthly Maintenance

  1. Review security: Check firewall rules and fail2ban status
  2. Update certificates: Ensure SSL certificates are valid (domain users only)
  3. Clean up old images: Run the cleanup script to remove unused Docker images
    # On CI/CD server
    cd /opt/registry
    ./scripts/cleanup.sh --type ci-cd
    
    # On Production server
    cd /opt/APP_NAME
    ./scripts/cleanup.sh --type production
    
  4. Review monitoring: Check application performance and logs
  5. Verify registry access: Test registry connectivity and authentication

Summary

Your complete CI/CD pipeline is now ready! The setup includes:

CI/CD Linode Features

  • Forgejo Actions runner for automated builds
  • Local Docker registry with web UI for image management
  • Secure SSH communication with production server
  • Monitoring and cleanup scripts
  • Firewall protection for security

Production Linode Features

  • Docker-based application deployment
  • Nginx reverse proxy with security headers
  • Automated backups and monitoring scripts
  • Firewall and fail2ban protection for security
  • Optional SSL/TLS certificate management (if domain is provided)

Pipeline Features

  • Automated testing on every code push
  • Automated image building and registry push
  • Automated deployment to production
  • Rollback capability with image versioning
  • Health monitoring and logging

Registry Integration

  • Private registry on CI/CD Linode stores all production images
  • Images available for manual deployment via PRODUCTION_LINODE_MANUAL_SETUP.md
  • Version control with git commit SHA tags
  • Web UI for image management at http://YOUR_CI_CD_IP:8080

Access Methods

  • Domain users: Access via https://your-domain.com
  • IP-only users: Access via http://YOUR_PRODUCTION_IP
  • Registry UI: Access via http://YOUR_CI_CD_IP:8080

For ongoing maintenance and troubleshooting, refer to the troubleshooting section and monitoring scripts provided in this guide.