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
- Code Push: Developer pushes code to Forgejo repository
- Automated Testing: CI/CD Linode runs tests on backend and frontend
- Image Building: If tests pass, Docker images are built
- Registry Push: Images are pushed to private registry on CI/CD Linode
- Production Deployment: Production Linode pulls images and deploys
- 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
- Set up CI/CD Linode (Steps 1-13)
- Set up Production Linode (Steps 14-26)
- Configure SSH key exchange (Step 27)
- Set up Forgejo repository secrets (Step 28)
- 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
-
Root User
- Purpose: Initial system setup only
- SSH Access: Disabled after setup
- Privileges: Full system access (used only during initial configuration)
-
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
-
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:
- Created two Ubuntu 24.04 LTS Linodes with root access
- Set root passwords for both Linodes
- Have SSH client installed on your local machine
- Have Forgejo repository with Actions enabled
- 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:
-
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
- Replace
-
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
- Replace
-
Application Name: Choose a name for your application (e.g.,
myapp
,webapp
,api
)- Replace
APP_NAME
in this guide with your chosen name
- Replace
-
Domain Name (Optional): If you have a domain, note it for SSL configuration
- Replace
your-domain.com
in this guide with your actual domain
- Replace
Example:
- If you choose
appuser
as service account,deploy
as deployment user, andmyapp
as application name:- Replace all
SERVICE_USER
withappuser
- Replace all
DEPLOY_USER
withdeploy
- Replace all
APP_NAME
withmyapp
- If you have a domain
example.com
, replaceyour-domain.com
withexample.com
- Replace all
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 bothregistry
andregistry-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
- Go to your Forgejo repository
- Navigate to Settings → Actions → Runners
- Click "New runner"
- 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 namePOSTGRES_USER=sharenet
- PostgreSQL usernamePOSTGRES_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 supportscripts/deploy-local.sh
- Local development deployment scriptscripts/migrate.sh
- Database migration managementscripts/validate_migrations.sh
- Migration validationscripts/monitor.sh
- Comprehensive monitoring script for both CI/CD and production environmentsscripts/cleanup.sh
- Comprehensive cleanup script for both CI/CD and production environmentsscripts/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:
- Go to your Forgejo repository
- Navigate to Settings → Secrets and Variables → Actions
- Click New Secret
- Enter the secret name and value
- 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
- Go to your Forgejo repository
- Navigate to Actions tab
- Monitor the workflow execution
- 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
-
Docker permission denied:
sudo usermod -aG docker SERVICE_USER newgrp docker
-
SSL certificate issues (domain users only):
sudo certbot certificates sudo certbot renew --dry-run
-
Application not starting:
cd /opt/APP_NAME docker-compose logs
-
SSH connection failed:
ssh -v ci-cd 'echo test' ssh -v production 'echo test'
-
Registry connection failed:
curl -v http://YOUR_CI_HOST_IP:5000/v2/_catalog
-
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
- Service Account: Use dedicated
SERVICE_USER
user with limited privileges - SSH Keys: Use Ed25519 keys with proper permissions (600/700)
- Firewall: Configure UFW to allow only necessary ports
- Fail2ban: Protect against brute force attacks
- SSL/TLS: Use Let's Encrypt certificates with automatic renewal (domain users only)
- Regular Backups: Automated daily backups of database and configuration
- Container Isolation: Applications run in isolated Docker containers
- Security Headers: Nginx configured with security headers
- 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
- Check disk space:
df -h
- Review logs:
docker-compose logs --tail=100
- Update system:
sudo apt update && sudo apt upgrade
- 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
- Review security: Check firewall rules and fail2ban status
- Update certificates: Ensure SSL certificates are valid (domain users only)
- 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
- Review monitoring: Check application performance and logs
- 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.