diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml index 63f33f9..0513b6f 100644 --- a/.forgejo/workflows/ci.yml +++ b/.forgejo/workflows/ci.yml @@ -8,7 +8,7 @@ on: env: REGISTRY: ${{ secrets.CI_HOST }}:5000 - IMAGE_NAME: sharenet + IMAGE_NAME: ${{ secrets.APP_NAME || 'sharenet' }} jobs: test-backend: @@ -20,7 +20,7 @@ jobs: image: postgres:15 env: POSTGRES_PASSWORD: postgres - POSTGRES_DB: sharenet_test + POSTGRES_DB: ${{ secrets.APP_NAME || 'sharenet' }}_test options: >- --health-cmd pg_isready --health-interval 10s @@ -135,10 +135,19 @@ jobs: username: ${{ secrets.PROD_USER }} key: ${{ secrets.PROD_SSH_KEY }} script: | - cd /opt/sharenet + cd /opt/${{ secrets.APP_NAME || 'sharenet' }} + + # Pull latest code from repository (includes docker-compose.yml and nginx config) + git pull origin main + + # Create environment file for this deployment echo "IMAGE_TAG=${{ github.sha }}" > .env echo "REGISTRY=${{ secrets.CI_HOST }}:5000" >> .env - echo "IMAGE_NAME=sharenet" >> .env - docker-compose pull - docker-compose up -d - docker system prune -f \ No newline at end of file + echo "IMAGE_NAME=${{ secrets.APP_NAME || 'sharenet' }}" >> .env + echo "POSTGRES_PASSWORD=${{ secrets.POSTGRES_PASSWORD || 'your_secure_password_here' }}" >> .env + echo "DATABASE_URL=postgresql://${{ secrets.POSTGRES_USER || 'sharenet' }}:${{ secrets.POSTGRES_PASSWORD || 'your_secure_password_here' }}@postgres:5432/${{ secrets.POSTGRES_DB || 'sharenet' }}" >> .env + echo "NODE_ENV=production" >> .env + echo "RUST_LOG=info" >> .env + + # Run deployment using the repository's docker-compose.yml + sudo ./deploy.sh \ No newline at end of file diff --git a/CI_CD_PIPELINE_SETUP_GUIDE.md b/CI_CD_PIPELINE_SETUP_GUIDE.md new file mode 100644 index 0000000..c9e6299 --- /dev/null +++ b/CI_CD_PIPELINE_SETUP_GUIDE.md @@ -0,0 +1,1676 @@ +# 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: + +```bash +# 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: + +```bash +# 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: + +```bash +# 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): +```bash +# 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: + +```bash +# 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: + +```bash +# 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: + +```bash +# 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: +```bash +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: + +```bash +# 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: + +```bash +# 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: +```bash +ssh ci-cd-dev +ssh production-dev +``` + +--- + +## Part 1: CI/CD Linode Setup + +### Step 1: Initial System Setup + +#### 1.1 Update the System + +```bash +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 Install Essential Packages + +```bash +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 + +```bash +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 + +```bash +sudo su - SERVICE_USER +whoami +pwd +exit + +sudo su - DEPLOY_USER +whoami +pwd +exit +``` + +### Step 3: Install Docker + +#### 3.1 Add Docker Repository + +```bash +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 + +```bash +sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin +``` + +#### 3.3 Configure Docker for Service Account + +```bash +sudo usermod -aG docker SERVICE_USER +``` + +### Step 4: Set Up Docker Registry + +#### 4.1 Create Registry Directory + +```bash +sudo mkdir -p /opt/registry +sudo chown SERVICE_USER:SERVICE_USER /opt/registry +``` + +#### 4.2 Create Registry Configuration + +```bash +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 + +```bash +# 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 + +```bash +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 + +```bash +# Install htpasswd utility +sudo apt install -y apache2-utils +``` + +#### 4.6 Start Registry + +```bash +cd /opt/registry +docker-compose up -d +``` + +#### 4.7 Test Registry Setup + +```bash +# 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 + +```bash +# 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 + +```bash +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: +```bash +# 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): +```bash +# 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: +```bash +# 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 + +```bash +ssh-keygen -t ed25519 -C "ci-cd-server" -f ~/.ssh/id_ed25519 -N "" +``` + +#### 6.2 Create SSH Config + +```bash +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 + +```bash +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 + +```bash +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 + +```bash +sudo systemctl daemon-reload +sudo systemctl enable forgejo-runner.service +``` + +#### 7.4 Test Runner Configuration + +```bash +# 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 Create Monitoring Script + +```bash +cat > ~/monitor.sh << 'EOF' +#!/bin/bash + +echo "=== CI/CD Server Status ===" +echo "Date: $(date)" +echo "Uptime: $(uptime)" +echo "" + +echo "=== Docker Status ===" +docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" +echo "" + +echo "=== Registry Status ===" +cd /opt/registry +docker-compose ps +echo "" + +echo "=== Actions Runner Status ===" +sudo systemctl status forgejo-runner.service --no-pager +echo "" + +echo "=== System Resources ===" +echo "CPU Usage:" +top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1 +echo "Memory Usage:" +free -h | grep Mem +echo "Disk Usage:" +df -h / +echo "" + +echo "=== Recent Logs ===" +docker-compose logs --tail=10 +EOF + +chmod +x ~/monitor.sh +``` + +#### 8.2 Create Cleanup Script + +```bash +cat > ~/cleanup.sh << 'EOF' +#!/bin/bash + +echo "Cleaning up old Docker images..." + +# Remove unused images +docker image prune -f + +# Remove unused volumes +docker volume prune -f + +# Remove unused networks +docker network prune -f + +# Remove old registry images (keep last 10 tags per repository) +cd /opt/registry +docker-compose exec registry registry garbage-collect /etc/docker/registry/config.yml + +echo "Cleanup complete!" +EOF + +chmod +x ~/cleanup.sh +``` + +#### 8.3 Test Cleanup Script + +```bash +# 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 + +# Run the cleanup script +./cleanup.sh + +# 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 ~/cleanup.sh` +- Verify Docker access: `docker ps` +- Check registry access: `cd /opt/registry && docker-compose ps` +- Run manually: `bash -x ~/cleanup.sh` + +#### 8.4 Set Up Automated Cleanup + +```bash +(crontab -l 2>/dev/null; echo "0 3 * * * /home/SERVICE_USER/cleanup.sh") | crontab - +``` + +### Step 9: Configure Firewall + +```bash +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 + +```bash +docker --version +docker-compose --version +``` + +#### 10.2 Check Registry Status + +```bash +cd /opt/registry +docker-compose ps +``` + +#### 10.3 Test Registry Access + +```bash +curl http://localhost:5000/v2/_catalog +``` + +#### 10.4 Get Public Key for Production Server + +```bash +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 + +```bash +# 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 + +```bash +sudo systemctl start forgejo-runner.service +sudo systemctl status forgejo-runner.service +``` + +#### 11.4 Test Runner Configuration + +```bash +# 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 + +```bash +sudo apt update && sudo apt upgrade -y +``` + +#### 12.2 Install Essential Packages + +```bash +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 + +```bash +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 + +```bash +sudo su - SERVICE_USER +whoami +pwd +exit + +sudo su - DEPLOY_USER +whoami +pwd +exit +``` + +### Step 14: Install Docker + +#### 14.1 Add Docker Repository + +```bash +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 + +```bash +sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin +``` + +#### 14.3 Configure Docker for Service Account + +```bash +sudo usermod -aG docker SERVICE_USER +``` + +### Step 15: Install Docker Compose + +```bash +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 + +```bash +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 + +```bash +sudo systemctl enable fail2ban +sudo systemctl start fail2ban +``` + +### Step 17: Create Application Directory + +#### 17.1 Create Directory Structure + +```bash +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) + +```bash +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 + +```bash +sudo su - SERVICE_USER +``` + +#### 18.2 Clone Repository + +```bash +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: + +```bash +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 +DATABASE_URL=postgresql://SERVICE_USER:your_secure_password_here@postgres:5432/APP_NAME + +# Application Configuration +NODE_ENV=production +RUST_LOG=info +EOF +``` + +**Important**: Replace `YOUR_CI_CD_IP` with your actual CI/CD Linode IP address. + +**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. + +#### 18.4 Verify Repository Contents + +```bash +# 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 Create Deployment Script + +```bash +cat > /opt/APP_NAME/deploy.sh << 'EOF' +#!/bin/bash + +# Deployment script for APP_NAME +set -e + +echo "Deploying APP_NAME..." + +# Pull latest code from repository +git pull origin main + +# Pull latest images +docker-compose pull + +# Stop existing containers +docker-compose down + +# Start new containers +docker-compose up -d + +# Clean up old images +docker image prune -f + +# Verify deployment +echo "Verifying deployment..." +sleep 10 # Give containers time to start +if docker-compose ps | grep -q "Up"; then + echo "Deployment successful! All containers are running." + docker-compose ps +else + echo "Deployment failed! Some containers are not running." + docker-compose ps + docker-compose logs --tail=20 + exit 1 +fi + +echo "Deployment complete!" +EOF + +chmod +x /opt/APP_NAME/deploy.sh +``` + +#### 18.6 Create Backup Script + +```bash +cat > /opt/APP_NAME/backup.sh << 'EOF' +#!/bin/bash + +# Backup script for APP_NAME +set -e + +BACKUP_DIR="/opt/APP_NAME/backups" +DATE=$(date +%Y%m%d_%H%M%S) + +mkdir -p $BACKUP_DIR + +# Backup database +docker-compose exec -T postgres pg_dump -U ${POSTGRES_USER:-sharenet} ${POSTGRES_DB:-sharenet} > $BACKUP_DIR/db_backup_$DATE.sql + +# Backup configuration files +tar -czf $BACKUP_DIR/config_backup_$DATE.tar.gz .env docker-compose.yml nginx/ + +# Keep only last 7 days of backups +find $BACKUP_DIR -name "*.sql" -mtime +7 -delete +find $BACKUP_DIR -name "*.tar.gz" -mtime +7 -delete + +echo "Backup completed: $BACKUP_DIR" +EOF + +chmod +x /opt/APP_NAME/backup.sh +``` + +#### 18.6.1 Set Up Automated Backup Scheduling + +```bash +# Create a cron job to run backups daily at 2 AM +(crontab -l 2>/dev/null; echo "0 2 * * * /opt/APP_NAME/backup.sh >> /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 + +**To test the backup manually:** +```bash +cd /opt/APP_NAME +./backup.sh +``` + +**To view backup logs:** +```bash +tail -f /opt/APP_NAME/backup.log +``` + +#### 18.7 Create Monitoring Script + +```bash +cat > /opt/APP_NAME/monitor.sh << 'EOF' +#!/bin/bash + +# Monitoring script for APP_NAME +echo "=== APP_NAME Application Status ===" +echo + +echo "Container Status:" +docker-compose ps +echo + +echo "Recent Logs:" +docker-compose logs --tail=20 +echo + +echo "System Resources:" +echo "CPU Usage:" +top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1 +echo + +echo "Memory Usage:" +free -h +echo + +echo "Disk Usage:" +df -h +echo + +echo "Network Connections:" +netstat -tuln | grep -E ':(80|443|3000|3001)' +EOF + +chmod +x /opt/APP_NAME/monitor.sh +``` + +#### 18.7.1 Set Up Automated Monitoring + +```bash +# Create a cron job to run monitoring every 5 minutes +(crontab -l 2>/dev/null; echo "*/5 * * * * /opt/APP_NAME/monitor.sh >> /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 + +**To test the monitoring manually:** +```bash +cd /opt/APP_NAME +./monitor.sh +``` + +**To view monitoring logs:** +```bash +tail -f /opt/APP_NAME/monitor.log +``` + +### Step 19: Set Up SSH for CI/CD Communication + +#### 19.1 Generate SSH Key Pair + +```bash +ssh-keygen -t ed25519 -C "production-server" -f ~/.ssh/id_ed25519 -N "" +``` + +#### 19.2 Create SSH Config + +```bash +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 + +```bash +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 + +```bash +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 + +```bash +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 + +```bash +sudo certbot --nginx -d your-domain.com +``` + +#### 22.2 Copy SSL Certificates + +```bash +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 + +```bash +ssh ci-cd 'echo Connection successful' +``` + +**Expected output**: `Connection successful`. + +#### 23.2 Test Registry Connection + +```bash +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 **Settings** → **Secrets and Variables** → **Actions** +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: + +```bash +# 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: + +```bash +# 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:** +```bash +# Test HTTP redirect to HTTPS +curl -I http://your-domain.com + +# Test HTTPS access +curl -I https://your-domain.com + +# Test application endpoints +curl -I https://your-domain.com/api/health +``` + +**If you don't have a domain (IP access only):** +```bash +# Test HTTP access via IP +curl -I http://YOUR_PRODUCTION_IP + +# Test application endpoints +curl -I http://YOUR_PRODUCTION_IP/api/health +``` + +### Step 27: Test Monitoring + +```bash +# On CI/CD server +cd /opt/registry +./monitor.sh + +# On Production server +cd /opt/APP_NAME +./monitor.sh +``` + +### Step 28: Test Registry Access + +```bash +# 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**: + ```bash + sudo usermod -aG docker SERVICE_USER + newgrp docker + ``` + +2. **SSL certificate issues** (domain users only): + ```bash + sudo certbot certificates + sudo certbot renew --dry-run + ``` + +3. **Application not starting**: + ```bash + cd /opt/APP_NAME + docker-compose logs + ``` + +4. **SSH connection failed**: + ```bash + ssh -v ci-cd 'echo test' + ssh -v production 'echo test' + ``` + +5. **Registry connection failed**: + ```bash + curl -v http://YOUR_CI_HOST_IP:5000/v2/_catalog + ``` + +6. **Actions runner not starting**: + ```bash + 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 + +```bash +# On CI/CD server +cd /opt/registry +./monitor.sh + +# On Production server +cd /opt/APP_NAME +./monitor.sh +``` + +#### 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 + +#### 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**: Remove unused Docker images +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. \ No newline at end of file diff --git a/CI_CD_SETUP_GUIDE.md b/CI_CD_SETUP_GUIDE.md deleted file mode 100644 index a706c47..0000000 --- a/CI_CD_SETUP_GUIDE.md +++ /dev/null @@ -1,819 +0,0 @@ -# CI/CD Linode Setup Guide - -This guide covers setting up your CI/CD Linode with Forgejo Actions runner and a local Docker registry for automated deployments. - -## Architecture Overview - -``` -┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ -│ Forgejo Host │ │ CI/CD Linode │ │ Production Linode│ -│ (Repository) │ │ (Actions Runner)│ │ (Docker Deploy) │ -│ │ │ + Docker Registry│ │ │ -└─────────────────┘ └─────────────────┘ └─────────────────┘ - │ │ │ - │ │ │ - └─────────── Push ──────┼───────────────────────┘ - │ - └─── Deploy ────────────┘ -``` - -## Prerequisites - -- Ubuntu 24.04 LTS Linode with root access -- Basic familiarity with Linux commands and SSH -- Forgejo repository with Actions enabled -- Production Linode IP address (for SSH key exchange) - -## Quick Start - -1. **Follow this complete CI/CD setup guide** -2. **Set up SSH keys** for secure communication with Production server -3. **Configure Forgejo Actions runner** -4. **Test registry and runner functionality** -5. **Exchange SSH keys** with Production server -6. **Configure Forgejo repository secrets** - -## 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 - -## Prerequisites and Initial Setup - -### What's Already Done (Assumptions) - -This guide assumes you have already: - -1. **Created an Ubuntu 24.04 LTS Linode** with root access -2. **Set root password** for the Linode -3. **Have SSH client** installed on your local machine -4. **Have Production Linode IP address** ready for SSH key exchange - -**Note**: The CI/CD Linode will be accessed via IP address only, as it's primarily used for internal services (Docker registry, Forgejo Actions runner) that don't require public web access. - -### Step 0: Initial SSH Access and Verification - -Before proceeding with the setup, you need to establish initial SSH access to your CI/CD Linode. - -#### 0.1 Get Your Linode IP Address - -From your Linode dashboard, note the IP address for: -- **CI/CD Linode**: `YOUR_CI_CD_IP` (IP address only, no domain needed) - -#### 0.2 Test Initial SSH Access - -Test SSH access to your CI/CD Linode: - -```bash -# Test CI/CD Linode (IP address only) -ssh root@YOUR_CI_CD_IP -``` - -**Expected output**: SSH login prompt asking for root password. - -**If something goes wrong**: -- Verify the IP address is correct -- Check that SSH is enabled on the Linode -- Ensure your local machine can reach the Linode (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 - -2. **Application Name**: Choose a name for your application (e.g., `myapp`, `webapp`, `api`) - - Replace `APP_NAME` in this guide with your chosen name - -**Example**: -- If you choose `appuser` as service account and `myapp` as application name: - - Replace all `SERVICE_USER` with `appuser` - - Replace all `APP_NAME` with `myapp` - ---- - -## CI/CD Linode Setup - -### Step 1: Initial System Setup - -#### 1.1 Update the System - -```bash -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. - -**If something goes wrong**: -- Check your internet connection -- Try running `sudo apt update` first, then `sudo apt upgrade -y` separately -- If you get package conflicts, run `sudo apt --fix-broken install` - -#### 1.2 Install Essential Packages - -```bash -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. - -**Expected output**: Package installation progress, ending with completion messages. - -**If something goes wrong**: -- Check if any packages failed to install -- Run `sudo apt install -f` to fix broken dependencies -- Ensure you have sufficient disk space: `df -h` - -### Step 2: Create Service Account - -#### 2.1 Create the SERVICE_USER User - -```bash -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 -``` - -**What this does**: -- Creates a dedicated service account named `SERVICE_USER` -- Gives it sudo privileges for administrative tasks -- Generates a random 32-character password - -**Expected output**: No output (successful user creation is silent). - -**If something goes wrong**: -- If user already exists: `sudo userdel -r SERVICE_USER` then retry -- Check user creation: `id SERVICE_USER` -- Verify sudo access: `sudo -u SERVICE_USER sudo -l` - -#### 2.2 Verify Service Account - -```bash -sudo su - SERVICE_USER -whoami -pwd -``` - -**What this does**: Switches to the SERVICE_USER user and verifies the setup. - -**Expected output**: -``` -SERVICE_USER -/home/SERVICE_USER -``` - -### Step 3: Install Docker - -#### 3.1 Add Docker Repository - -```bash -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 -``` - -**What this does**: Adds Docker's official repository to your system for the latest Docker versions. - -**Expected output**: GPG key import confirmation and package list update. - -#### 3.2 Install Docker Packages - -```bash -sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin -``` - -**What this does**: Installs Docker Engine, CLI, container runtime, and Docker Compose. - -**Expected output**: Package installation progress, ending with completion messages. - -#### 3.3 Configure Docker for Service Account - -```bash -sudo usermod -aG docker SERVICE_USER -``` - -**What this does**: Adds the SERVICE_USER user to the docker group so it can run Docker commands without sudo. - -### Step 4: Install Docker Compose - -```bash -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 -``` - -**What this does**: Downloads the latest Docker Compose binary and makes it executable. - -### Step 5: Configure Docker for Local Registry - -```bash -echo '{"insecure-registries": ["localhost:5000"]}' | sudo tee /etc/docker/daemon.json -sudo systemctl restart docker -``` - -**What this does**: Configures Docker to allow connections to the local registry without SSL verification. - -### Step 6: Install Development Tools - -#### 6.1 Install Rust - -```bash -sudo su - SERVICE_USER -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y -source $HOME/.cargo/env -``` - -**What this does**: Installs Rust programming language and Cargo package manager for building the backend. - -#### 6.2 Install Node.js - -```bash -curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - -sudo apt install -y nodejs -``` - -**What this does**: Installs Node.js version 20 for building the frontend. - -### Step 7: Set Up Docker Registry - -#### 7.1 Create Registry Directory - -```bash -sudo mkdir -p /opt/registry -sudo chown SERVICE_USER:SERVICE_USER /opt/registry -``` - -**What this does**: Creates a directory for the Docker registry and sets ownership to the SERVICE_USER user. - -#### 7.2 Create Docker Compose File for Registry - -```bash -cat > /opt/registry/docker-compose.yml << 'EOF' -version: '3.8' - -services: - registry: - image: registry:2 - container_name: docker-registry - restart: unless-stopped - ports: - - "5000:5000" - environment: - REGISTRY_STORAGE_DELETE_ENABLED: "true" - REGISTRY_STORAGE_FILESYSTEM_MAXTHREADS: "100" - volumes: - - registry_data:/var/lib/registry - - ./config.yml:/etc/docker/registry/config.yml:ro - networks: - - registry-network - - registry-ui: - image: joxit/docker-registry-ui:latest - container_name: registry-ui - restart: unless-stopped - ports: - - "8080:80" - environment: - REGISTRY_URL: http://registry:5000 - DELETE_IMAGES: "true" - depends_on: - - registry - networks: - - registry-network - -volumes: - registry_data: - driver: local - -networks: - registry-network: - driver: bridge -EOF -``` - -#### 7.3 Create Registry Configuration - -```bash -cat > /opt/registry/config.yml << 'EOF' -version: 0.1 -log: - fields: - service: registry -storage: - delete: - enabled: true - cache: - blobdescriptor: inmemory - filesystem: - rootdirectory: /var/lib/registry -http: - addr: :5000 - headers: - X-Content-Type-Options: [nosniff] -health: - storagedriver: - enabled: true - interval: 10s - threshold: 3 -EOF -``` - -#### 7.4 Start the Registry - -```bash -cd /opt/registry -docker-compose up -d -``` - -**What this does**: Starts the Docker registry and web UI in detached mode. - -**Expected output**: Container creation and startup messages. - -### Step 8: Set Up SSH Keys - -#### 8.1 Create SSH Directory - -```bash -mkdir -p ~/.ssh -chmod 700 ~/.ssh -``` - -#### 8.2 Generate SSH Key Pair - -```bash -ssh-keygen -t ed25519 -C "ci-cd-server" -f ~/.ssh/id_ed25519 -N "" -``` - -**What this does**: Generates an Ed25519 SSH key pair for secure communication with the production server. - -**Expected output**: Key generation messages and file creation confirmation. - -#### 8.3 Create SSH Config - -```bash -cat > ~/.ssh/config << 'EOF' -Host production - HostName YOUR_PRODUCTION_IP - User SERVICE_USER - IdentityFile ~/.ssh/id_ed25519 - StrictHostKeyChecking no - UserKnownHostsFile /dev/null -EOF - -chmod 600 ~/.ssh/config -``` - -**What this does**: Creates SSH configuration for easy connection to the production server. - -**Note**: You'll update `YOUR_PRODUCTION_IP` with the actual production server IP later. - -### Step 9: Install Forgejo Actions Runner - -#### 9.1 Download Runner - -```bash -cd ~ -wget https://github.com/actions/runner/releases/download/v2.311.0/actions-runner-linux-x64-2.311.0.tar.gz -tar -xzf actions-runner-linux-x64-2.311.0.tar.gz -rm actions-runner-linux-x64-2.311.0.tar.gz -``` - -**What this does**: Downloads and extracts the Forgejo Actions runner. - -#### 9.2 Create Systemd Service - -```bash -sudo tee /etc/systemd/system/github-runner.service > /dev/null << 'EOF' -[Unit] -Description=GitHub Actions Runner -After=network.target - -[Service] -Type=simple -User=SERVICE_USER -WorkingDirectory=/home/SERVICE_USER/actions-runner -ExecStart=/home/SERVICE_USER/actions-runner/run.sh -Restart=always -RestartSec=10 - -[Install] -WantedBy=multi-user.target -EOF -``` - -**What this does**: Creates a systemd service to automatically start and manage the Actions runner. - -#### 9.3 Enable Service - -```bash -sudo systemctl daemon-reload -sudo systemctl enable github-runner.service -``` - -**What this does**: Enables the Actions runner service to start automatically on boot. - -### Step 10: Set Up Monitoring and Cleanup - -#### 10.1 Create Monitoring Script - -```bash -cat > ~/monitor.sh << 'EOF' -#!/bin/bash - -echo "=== CI/CD Server Status ===" -echo "Date: $(date)" -echo "Uptime: $(uptime)" -echo "" - -echo "=== Docker Status ===" -docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}" -echo "" - -echo "=== Registry Status ===" -cd /opt/registry -docker-compose ps -echo "" - -echo "=== Actions Runner Status ===" -sudo systemctl status github-runner.service --no-pager -echo "" - -echo "=== System Resources ===" -echo "CPU Usage:" -top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1 -echo "Memory Usage:" -free -h | grep Mem -echo "Disk Usage:" -df -h / -echo "" - -echo "=== Recent Logs ===" -docker-compose logs --tail=10 -EOF - -chmod +x ~/monitor.sh -``` - -**What this does**: Creates a monitoring script to check the status of all CI/CD services. - -#### 10.2 Create Cleanup Script - -```bash -cat > ~/cleanup.sh << 'EOF' -#!/bin/bash - -echo "Cleaning up old Docker images..." - -# Remove unused images -docker image prune -f - -# Remove unused volumes -docker volume prune -f - -# Remove unused networks -docker network prune -f - -# Remove old registry images (keep last 10 tags per repository) -cd /opt/registry -docker-compose exec registry registry garbage-collect /etc/docker/registry/config.yml - -echo "Cleanup complete!" -EOF - -chmod +x ~/cleanup.sh -``` - -**What this does**: Creates a cleanup script to remove old Docker images and registry data. - -#### 10.3 Set Up Automated Cleanup - -```bash -(crontab -l 2>/dev/null; echo "0 3 * * * /home/SERVICE_USER/cleanup.sh") | crontab - -``` - -**What this does**: Schedules daily cleanup at 3 AM. - -### Step 11: Configure Firewall - -```bash -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 -sudo ufw allow 8080/tcp # Registry UI -``` - -**What this does**: Configures firewall to allow only necessary ports. - -### Step 12: Test CI/CD Setup - -#### 12.1 Test Docker Installation - -```bash -docker --version -docker-compose --version -``` - -**Expected output**: Version information for both Docker and Docker Compose. - -#### 12.2 Check Registry Status - -```bash -cd /opt/registry -docker-compose ps -``` - -**Expected output**: Status of registry and registry-ui containers showing "Up". - -#### 12.3 Test Registry Access - -```bash -curl http://localhost:5000/v2/_catalog -``` - -**Expected output**: `{"repositories":[]}` (empty initially). - -#### 12.4 Get Public Key for Production Server - -```bash -cat ~/.ssh/id_ed25519.pub -``` - -**Important**: Copy this public key - you'll need it for the production server setup. - -### Step 13: Configure Forgejo Actions Runner - -#### 13.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 - -#### 13.2 Configure Runner - -```bash -cd ~/actions-runner -./config.sh --url https://your-forgejo-instance/your-username/APP_NAME --token YOUR_TOKEN -``` - -#### 13.3 Start Runner - -```bash -sudo systemctl start github-runner.service -sudo systemctl status github-runner.service -``` - ---- - -## SSH Key Exchange - -After setting up both servers, you need to exchange SSH public keys for secure communication: - -### Step 1: Get Public Keys from Both Servers - -```bash -# On CI/CD server (get your public key) -cat ~/.ssh/id_ed25519.pub - -# On Production server (get their public key) -# You'll need to get this from the Production server -``` - -### Step 2: Add Production Server's Public Key - -```bash -# On CI/CD server (add Production's public key) -echo "PRODUCTION_PUBLIC_KEY_HERE" >> ~/.ssh/authorized_keys -sed -i 's/YOUR_PRODUCTION_IP/YOUR_ACTUAL_PRODUCTION_IP/g' ~/.ssh/config -``` - -### Step 3: Test SSH Connection - -```bash -# Test from CI/CD to Production -ssh production 'echo Connection successful' -``` - -**Expected output**: `Connection successful`. - ---- - -## Registry Configuration - -The CI/CD registry is configured to allow connections from the production server. - -### Step 1: Verify Registry Configuration - -```bash -# Check registry is running -cd /opt/registry -docker-compose ps - -# Test registry API -curl http://localhost:5000/v2/_catalog -``` - -**Expected output**: -- Registry containers showing "Up" -- Registry API: `{"repositories":[]}` or list of images - -### Step 2: Verify Registry UI Access - -You can access the registry web interface to manage images: - -```bash -# Test registry UI -curl -I http://localhost:8080 -``` - -**Expected output**: HTTP response (may be 404 initially, which is normal). - -**Note**: The registry UI is accessible at `http://YOUR_CI_CD_IP:8080` for administrative purposes. - ---- - -## Forgejo Configuration - -### Step 1: Add Repository Secrets - -Go to your Forgejo repository → Settings → Secrets and Variables → Actions, and add: - -- `CI_HOST`: Your CI/CD Linode IP address -- `PROD_HOST`: Your production Linode IP -- `PROD_USER`: SSH username for production server (should be `SERVICE_USER`) -- `PROD_SSH_KEY`: SSH private key for deployment - -### Step 2: Generate SSH Key for Deployment - -The setup automatically generates SSH keys for the SERVICE_USER service account. For Forgejo Actions deployment, use the CI/CD server's private key: - -```bash -# On CI/CD server -cat ~/.ssh/id_ed25519 -``` - -Copy the entire private key content (including the BEGIN and END lines) for the `PROD_SSH_KEY` secret. - ---- - -## Testing and Verification - -### Step 1: Test Registry Functionality - -```bash -# Test registry API -curl http://localhost:5000/v2/_catalog - -# Test registry UI (optional) -curl -I http://localhost:8080 -``` - -**Expected output**: -- Registry API: `{"repositories":[]}` (empty initially) -- Registry UI: HTTP response (may be 404 initially, which is normal) - -### Step 2: Test Actions Runner - -```bash -# Check runner status -sudo systemctl status github-runner.service - -# Check runner logs -sudo journalctl -u github-runner.service -f -``` - -**Expected output**: Runner service showing as active and running. - -### Step 3: Test Monitoring Script - -```bash -./monitor.sh -``` - -**Expected output**: Status information for all CI/CD services. - -### Step 4: Test SSH Connection to Production - -```bash -# Test from CI/CD to Production -ssh production 'echo Connection successful' -``` - -**Expected output**: `Connection successful`. - -### Step 5: Trigger First Deployment - -#### 5.1 Push Code Changes - -Make a small change to your code and push to trigger the CI/CD pipeline: - -```bash -# In your local repository -echo "# Test deployment" >> README.md -git add README.md -git commit -m "Test CI/CD pipeline" -git push -``` - -#### 5.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 - -#### 5.3 Verify Deployment - -After successful pipeline execution: - -```bash -# Check if images were pushed to registry -curl http://localhost:5000/v2/_catalog - -# Check registry UI for new images -# Open http://YOUR_CI_CD_IP:8080 in browser -``` - ---- - -## Troubleshooting - -### Common Issues - -1. **Docker permission denied**: - ```bash - sudo usermod -aG docker SERVICE_USER - newgrp docker - ``` - -2. **Registry not accessible**: - ```bash - cd /opt/registry - docker-compose logs - ``` - -3. **Actions runner not starting**: - ```bash - sudo systemctl status github-runner.service - sudo journalctl -u github-runner.service -f - ``` - -4. **SSH key issues**: - ```bash - chmod 600 ~/.ssh/id_ed25519 - chmod 700 ~/.ssh - ``` - -5. **Registry connection failed**: - ```bash - curl -v http://localhost:5000/v2/_catalog - ``` - -### 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. **Container Isolation**: Registry runs in isolated Docker containers -5. **Regular Cleanup**: Automated daily cleanup of old images - ---- - -## Summary - -Your CI/CD Linode is now ready to handle automated builds and deployments! The setup includes: - -- **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 - -For ongoing maintenance and troubleshooting, refer to the troubleshooting section and monitoring scripts provided in this guide. \ No newline at end of file diff --git a/PRODUCTION_SETUP_GUIDE.md b/PRODUCTION_LINODE_MANUAL_SETUP.md similarity index 53% rename from PRODUCTION_SETUP_GUIDE.md rename to PRODUCTION_LINODE_MANUAL_SETUP.md index 3a70dd8..53dcf00 100644 --- a/PRODUCTION_SETUP_GUIDE.md +++ b/PRODUCTION_LINODE_MANUAL_SETUP.md @@ -1,59 +1,54 @@ -# Production Linode Setup Guide +# Production Linode Setup Guide - Manual Deployment -This guide covers setting up your Production Linode for hosting the APP_NAME application with Docker, SSL/TLS, and automated deployments. +This guide covers setting up your Production Linode for hosting the APP_NAME application with manual deployment by pulling Docker images from a private registry. ## Architecture Overview ``` ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ -│ Forgejo Host │ │ CI/CD Linode │ │ Production Linode│ -│ (Repository) │ │ (Actions Runner)│ │ (Docker Deploy) │ -│ │ │ + Docker Registry│ │ │ +│ Private Registry│ │ Production Linode│ │ Your Domain │ +│ (Docker Images) │ │ (Docker Deploy) │ │ (Optional SSL) │ +│ │ │ │ │ │ +│ │ │ │ │ │ +│ │ │ │ │ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │ │ │ │ │ - └─────────── Push ──────┼───────────────────────┘ + └─────────── Pull ──────┼───────────────────────┘ │ - └─── Deploy ────────────┘ + └─── Serve ─────────────┘ ``` ## Prerequisites - Ubuntu 24.04 LTS Linode with root access -- Domain name pointing to the Production Linode's IP addresses - Basic familiarity with Linux commands and SSH -- **For CI/CD Integration**: CI/CD Linode already set up and IP address ready -- **For Manual Deployment**: Docker registry access (local or remote) +- **Access to a private Docker registry** with authentication credentials +- Docker images already built and pushed to your registry +- **Optional**: Domain name pointing to the Production Linode's IP addresses (for SSL/TLS) ## Quick Start -### Basic Production Setup (Manual Deployment) -1. **Follow the basic production setup guide** -2. **Set up SSL certificates** for secure HTTPS access -3. **Configure application deployment** with Docker Compose -4. **Test application functionality** and monitoring - -### CI/CD Integration Setup (Automated Deployment) -1. **Complete basic production setup first** -2. **Exchange SSH keys** with CI/CD server -3. **Configure registry connection** to CI/CD server -4. **Set up Forgejo repository secrets** -5. **Test automated deployment pipeline** +1. **Complete basic production setup** (Steps 1-7) +2. **Configure registry authentication** (Step 8) +3. **Set up application deployment** (Step 9) +4. **Configure SSL certificates** (Step 10 - Optional) +5. **Test manual deployment** (Step 11) ## What's Included ### Production Linode Features - Docker-based application deployment -- SSL/TLS certificate management +- **Optional SSL/TLS certificate management** (if domain is provided) - Nginx reverse proxy with security headers - Automated backups and monitoring - Firewall and fail2ban protection -### CI/CD Integration Features (Optional) -- Secure SSH communication with CI/CD server -- Automated deployment from CI/CD pipeline -- Registry connection for image pulling -- Forgejo Actions integration +### Manual Deployment Features +- Private registry authentication +- Manual image pulling and deployment +- Image update and rollback capabilities +- Deployment verification and testing ## Prerequisites and Initial Setup @@ -63,12 +58,12 @@ This guide assumes you have already: 1. **Created an Ubuntu 24.04 LTS Linode** with root access 2. **Set root password** for the Linode -3. **Assigned DNS entries** for your domain pointing to the Production Linode's IPv4 and IPv6 addresses -4. **Have SSH client** installed on your local machine -5. **For CI/CD Integration**: CI/CD Linode is set up and running with IP address ready -6. **For Manual Deployment**: Have access to Docker images (local registry, Docker Hub, or other registry) +3. **Have SSH client** installed on your local machine +4. **Have Docker images built and pushed** to your private registry +5. **Have private registry credentials** ready for authentication +6. **Optional**: Assigned DNS entries for your domain pointing to the Production Linode's IP addresses -**Note**: The Production Linode needs a domain name for SSL certificates and public web access. +**Note**: A domain name is optional but recommended for SSL/TLS certificates and professional web access. If you don't have a domain, you can access the application via IP address. ### Step 0: Initial SSH Access and Verification @@ -77,7 +72,7 @@ Before proceeding with the setup, you need to establish initial SSH access to yo #### 0.1 Get Your Linode IP Address From your Linode dashboard, note the IP address for: -- **Production Linode**: `YOUR_PRODUCTION_IP` (IP address for SSH, domain for web access) +- **Production Linode**: `YOUR_PRODUCTION_IP` (IP address for SSH and web access) #### 0.2 Test Initial SSH Access @@ -95,9 +90,9 @@ ssh root@YOUR_PRODUCTION_IP - Check that SSH is enabled on the Linode - Ensure your local machine can reach the Linode (no firewall blocking) -#### 0.3 Verify DNS Configuration +#### 0.3 Optional: Verify DNS Configuration (If Using Domain) -On the Production Linode, verify DNS is properly configured for your domain: +If you have a domain name, verify DNS is properly configured for your domain: ```bash # Check if domain resolves to this server @@ -115,9 +110,9 @@ dig your-domain.com AAAA - Check DNS settings in your domain registrar - Wait for DNS propagation (can take up to 48 hours) -#### 0.4 Test Domain Accessibility +#### 0.4 Optional: Test Domain Accessibility (If Using Domain) -Test that your domain is accessible on the Production Linode: +If you have a domain name, test that your domain is accessible on the Production Linode: ```bash # Test HTTP access (should show default nginx/apache page or connection refused) @@ -146,16 +141,26 @@ Before proceeding, decide on: 2. **Application Name**: Choose a name for your application (e.g., `myapp`, `webapp`, `api`) - Replace `APP_NAME` in this guide with your chosen name +3. **Domain Name** (Optional): If you have a domain, note it for SSL configuration + - Replace `your-domain.com` in this guide with your actual domain + +4. **Registry Information**: Note your private registry details + - Registry URL: `your-registry.com:port` (replace with your actual registry) + - Username: `YOUR_REGISTRY_USERNAME` (replace with your actual username) + - Password/Token: `YOUR_REGISTRY_PASSWORD` (replace with your actual password/token) + **Example**: - If you choose `appuser` as service account and `myapp` as application name: - Replace all `SERVICE_USER` with `appuser` - Replace all `APP_NAME` with `myapp` + - If you have a domain `example.com`, replace `your-domain.com` with `example.com` + - If your registry is `registry.company.com:5000`, replace `your-registry.com:port` with `registry.company.com:5000` --- ## Basic Production Setup -This section covers the essential setup for hosting your application. **Skip to the next section if you only want manual deployments.** +This section covers the essential setup for hosting your application with manual deployment. ### Step 1: Initial System Setup @@ -317,14 +322,14 @@ sudo chown SERVICE_USER:SERVICE_USER /opt/APP_NAME **What this does**: Creates a directory for the application and sets ownership to the SERVICE_USER user. -#### 6.2 Create SSL Directory +#### 6.2 Create SSL Directory (Optional - for domain users) ```bash sudo mkdir -p /opt/APP_NAME/nginx/ssl sudo chown SERVICE_USER:SERVICE_USER /opt/APP_NAME/nginx/ssl ``` -**What this does**: Creates a directory for SSL certificates. +**What this does**: Creates a directory for SSL certificates (only needed if using a domain). ### Step 7: Set Up Application Files @@ -340,7 +345,7 @@ sudo su - SERVICE_USER cat > /opt/APP_NAME/.env << 'EOF' # Production Environment Variables POSTGRES_PASSWORD=your_secure_password_here -REGISTRY=your-remote-registry.com:port +REGISTRY=your-registry.com:port IMAGE_NAME=APP_NAME IMAGE_TAG=latest @@ -355,34 +360,20 @@ EOF **What this does**: Creates environment variables for the application deployment. -**Note**: For manual deployments, you'll typically: -- **Pull images from a remote registry** (private registry, GitHub Container Registry, etc.) -- **Update `your-remote-registry.com:port`** with your actual registry URL -- **Configure docker-compose.yml** to reference images from your registry +**Note**: You'll need to update this with your actual registry information: +- Replace `your-registry.com:port` with your actual private registry URL +- Replace `your_secure_password_here` with a strong database password -**Examples for different registry types**: +**Registry URL Examples**: ```bash -# Private registry +# Private registry with custom port REGISTRY=registry.company.com:5000 -# GitHub Container Registry -REGISTRY=ghcr.io +# Private registry with standard HTTPS port +REGISTRY=registry.company.com -# GitLab Container Registry -REGISTRY=registry.gitlab.com - -# AWS ECR -REGISTRY=123456789012.dkr.ecr.us-east-1.amazonaws.com -``` - -**Examples for docker-compose.yml**: -```yaml -# For remote registry: -services: - backend: - image: your-remote-registry.com:port/APP_NAME-backend:latest - frontend: - image: your-remote-registry.com:port/APP_NAME-frontend:latest +# Private registry with custom domain +REGISTRY=docker.company.internal ``` #### 7.3 Create Deployment Script @@ -455,38 +446,33 @@ chmod +x /opt/APP_NAME/backup.sh cat > /opt/APP_NAME/monitor.sh << 'EOF' #!/bin/bash +# Monitoring script for APP_NAME echo "=== APP_NAME Application Status ===" -echo "Date: $(date)" -echo "Uptime: $(uptime)" -echo "" +echo -echo "=== Docker Containers ===" +echo "Container Status:" docker-compose ps -echo "" +echo -echo "=== Application Logs (last 20 lines) ===" +echo "Recent Logs:" docker-compose logs --tail=20 -echo "" +echo -echo "=== System Resources ===" +echo "System Resources:" echo "CPU Usage:" top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1 +echo + echo "Memory Usage:" -free -h | grep Mem +free -h +echo + echo "Disk Usage:" -df -h / -echo "" +df -h +echo -echo "=== Network Status ===" -netstat -tlnp | grep -E ':(80|443|3000|3001)' -echo "" - -echo "=== SSL Certificate Status ===" -if [ -f "/opt/APP_NAME/nginx/ssl/cert.pem" ]; then - openssl x509 -in /opt/APP_NAME/nginx/ssl/cert.pem -text -noout | grep -E "(Not Before|Not After)" -else - echo "SSL certificate not found" -fi +echo "Network Connections:" +netstat -tuln | grep -E ':(80|443|3000|3001)' EOF chmod +x /opt/APP_NAME/monitor.sh @@ -494,241 +480,339 @@ chmod +x /opt/APP_NAME/monitor.sh **What this does**: Creates a monitoring script to check application and system status. -### Step 8: Set Up Automated Backups +--- + +## Manual Deployment Setup + +This section covers setting up the Production Linode for manual deployment from a private registry. + +### Step 8: Configure Registry Authentication + +#### 8.1 Login to Your Private Registry ```bash -sudo -u SERVICE_USER bash -c '(crontab -l 2>/dev/null; echo "0 2 * * * /opt/APP_NAME/backup.sh") | crontab -' +docker login your-registry.com:port -u YOUR_REGISTRY_USERNAME -p YOUR_REGISTRY_PASSWORD ``` -**What this does**: Schedules daily backups at 2 AM. +**What this does**: Authenticates with your private Docker registry to allow pulling images. -### Step 9: Copy Application Files +**Expected output**: `Login Succeeded` message. + +**If something goes wrong**: +- Verify your credentials are correct +- Check network connectivity to the registry +- Ensure your registry account has access to the images +- Verify the registry URL and port are correct + +#### 8.2 Verify Authentication ```bash -sudo su - SERVICE_USER -cd /opt/APP_NAME -git clone https://your-forgejo-instance/your-username/APP_NAME.git . +# Check if you're logged in +cat ~/.docker/config.json + +# Test registry connectivity +curl -v https://your-registry.com:port/v2/_catalog ``` -**What this does**: Clones your application repository to the production server. +**What this does**: Verifies that authentication was successful and registry is accessible. -**Note**: Replace the URL with your actual repository URL. +**Note**: If your registry uses HTTP instead of HTTPS, use `http://` instead of `https://` in the curl command. -### Step 10: Set Up SSL Certificates +### Step 9: Set Up Application Deployment -#### 10.1 Install SSL Certificate +#### 9.1 Create Docker Compose File + +```bash +cat > /opt/APP_NAME/docker-compose.yml << 'EOF' +version: '3.8' + +services: + postgres: + image: postgres:15 + environment: + POSTGRES_DB: APP_NAME + POSTGRES_USER: SERVICE_USER + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + volumes: + - postgres_data:/var/lib/postgresql/data + restart: unless-stopped + networks: + - app_network + + backend: + image: ${REGISTRY}/APP_NAME-backend:${IMAGE_TAG} + environment: + DATABASE_URL: ${DATABASE_URL} + RUST_LOG: ${RUST_LOG} + depends_on: + - postgres + restart: unless-stopped + networks: + - app_network + + frontend: + image: ${REGISTRY}/APP_NAME-frontend:${IMAGE_TAG} + environment: + NODE_ENV: ${NODE_ENV} + depends_on: + - backend + restart: unless-stopped + networks: + - app_network + + nginx: + image: nginx:alpine + ports: + - "80:80" + - "443:443" + volumes: + - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro + - ./nginx/ssl:/etc/nginx/ssl:ro + depends_on: + - frontend + - backend + restart: unless-stopped + networks: + - app_network + +volumes: + postgres_data: + +networks: + app_network: + driver: bridge +EOF +``` + +**What this does**: Creates a Docker Compose configuration for your application. + +**Note**: Update the image names to match your actual registry and image names. + +#### 9.2 Create Nginx Configuration + +```bash +mkdir -p /opt/APP_NAME/nginx + +# Choose configuration based on whether you have a domain +if [ -n "$DOMAIN_NAME" ]; then + # Configuration with SSL for domain users + cat > /opt/APP_NAME/nginx/nginx.conf << 'EOF' +events { + worker_connections 1024; +} + +http { + upstream backend { + server backend:3001; + } + + upstream frontend { + server frontend:3000; + } + + # Redirect HTTP to HTTPS + server { + listen 80; + server_name your-domain.com; + return 301 https://$server_name$request_uri; + } + + # HTTPS server + server { + listen 443 ssl http2; + server_name your-domain.com; + + # SSL configuration + ssl_certificate /etc/nginx/ssl/fullchain.pem; + ssl_certificate_key /etc/nginx/ssl/privkey.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384; + ssl_prefer_server_ciphers off; + + # Security headers + add_header X-Frame-Options DENY; + add_header X-Content-Type-Options nosniff; + add_header X-XSS-Protection "1; mode=block"; + add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"; + + # API routes + location /api/ { + proxy_pass http://backend/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # Frontend routes + location / { + proxy_pass http://frontend/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + } +} +EOF +else + # Configuration without SSL for IP-only access + cat > /opt/APP_NAME/nginx/nginx.conf << 'EOF' +events { + worker_connections 1024; +} + +http { + upstream backend { + server backend:3001; + } + + upstream frontend { + server frontend:3000; + } + + # HTTP server (no SSL) + server { + listen 80; + server_name _; + + # Security headers + add_header X-Frame-Options DENY; + add_header X-Content-Type-Options nosniff; + add_header X-XSS-Protection "1; mode=block"; + + # API routes + location /api/ { + proxy_pass http://backend/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + # Frontend routes + location / { + proxy_pass http://frontend/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + } +} +EOF +fi +``` + +**What this does**: Creates Nginx configuration for reverse proxy. If you have a domain, it includes SSL configuration. If not, it serves HTTP only. + +**Note**: If you have a domain, replace `your-domain.com` with your actual domain name. + +### Step 10: Configure SSL Certificates (Optional - Domain Users Only) + +**Skip this step if you don't have a domain name.** + +#### 10.1 Install SSL Certificates ```bash sudo certbot --nginx -d your-domain.com ``` -**What this does**: Installs Let's Encrypt SSL certificate for your domain. +**What this does**: Obtains and installs SSL certificates from Let's Encrypt. -**Expected output**: Certificate installation confirmation and Nginx configuration updates. +**Expected output**: Certificate installation confirmation and Nginx configuration update. -#### 10.2 Copy Certificates +**If something goes wrong**: +- Verify DNS is pointing to this server +- Check that port 80 is accessible +- Ensure domain is not already using HTTPS + +#### 10.2 Copy SSL Certificates ```bash -sudo cp /etc/letsencrypt/live/your-domain.com/fullchain.pem /opt/APP_NAME/nginx/ssl/cert.pem -sudo cp /etc/letsencrypt/live/your-domain.com/privkey.pem /opt/APP_NAME/nginx/ssl/key.pem +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/* ``` -**What this does**: Copies SSL certificates to the application directory for Docker use. +**What this does**: Copies SSL certificates to the application directory for Docker access. -### Step 11: Test Basic Application +### Step 11: Manual Image Management -#### 11.1 Test Application Deployment +#### 11.1 Pull Images from Private Registry ```bash +# Pull backend image +docker pull your-registry.com:port/APP_NAME-backend:latest + +# Pull frontend image +docker pull your-registry.com:port/APP_NAME-frontend:latest + +# Verify images were pulled +docker images | grep APP_NAME +``` + +**What this does**: Downloads the latest images from your private registry. + +#### 11.2 Create Manual Pull and Deploy Script + +```bash +cat > /opt/APP_NAME/pull-and-deploy.sh << 'EOF' +#!/bin/bash + +# Manual pull and deploy script +set -e + +echo "Pulling APP_NAME images from private registry..." + +# Pull backend +docker pull your-registry.com:port/APP_NAME-backend:latest + +# Pull frontend +docker pull your-registry.com:port/APP_NAME-frontend:latest + +# Show pulled images +echo "Pulled images:" +docker images | grep APP_NAME + +# Deploy application cd /opt/APP_NAME ./deploy.sh -``` -**What this does**: Deploys the application using Docker Compose. - -**Expected output**: Deployment progress messages and completion. - -#### 11.2 Monitor Application - -```bash -cd /opt/APP_NAME -./monitor.sh -``` - -**Expected output**: Application status, system resources, and recent logs. - ---- - -## CI/CD Integration Setup (Optional) - -**⚠️ SKIP THIS SECTION if you're only doing manual deployments!** - -This section covers integrating with a CI/CD pipeline for automated deployments. - -### Prerequisites for CI/CD Integration - -- CI/CD Linode already set up and running -- CI/CD Linode IP address available -- SSH access to CI/CD server - -### Step 1: Configure Docker for CI/CD Registry - -#### 1.1 Update Docker Daemon Configuration - -```bash -# Configure Docker to allow connections to CI/CD registry -echo '{"insecure-registries": ["YOUR_CI_HOST_IP:5000"]}' | sudo tee /etc/docker/daemon.json -sudo systemctl restart docker -``` - -**What this does**: Configures Docker to allow connections to your CI/CD registry without SSL verification. - -**Note**: Replace `YOUR_CI_HOST_IP` with your actual CI/CD server IP. - -### Step 2: Set Up SSH Keys for CI/CD Communication - -#### 2.1 Create SSH Directory - -```bash -mkdir -p ~/.ssh -chmod 700 ~/.ssh -``` - -#### 2.2 Generate SSH Key Pair - -```bash -ssh-keygen -t ed25519 -C "production-server" -f ~/.ssh/id_ed25519 -N "" -``` - -**What this does**: Generates an Ed25519 SSH key pair for secure communication with the CI/CD server. - -#### 2.3 Create SSH Config - -```bash -cat > ~/.ssh/config << 'EOF' -Host ci-cd - HostName YOUR_CI_CD_IP - User SERVICE_USER - IdentityFile ~/.ssh/id_ed25519 - StrictHostKeyChecking no - UserKnownHostsFile /dev/null +echo "Pull and deploy complete!" EOF -chmod 600 ~/.ssh/config +chmod +x /opt/APP_NAME/pull-and-deploy.sh ``` -**What this does**: Creates SSH configuration for easy connection to the CI/CD server. +**What this does**: Creates a script to pull images from your private registry and deploy the application. -**Note**: Replace `YOUR_CI_CD_IP` with your actual CI/CD server IP. - -### Step 3: Exchange SSH Keys - -#### 3.1 Get Your Public Key +#### 11.3 Create Image Update Script ```bash -cat ~/.ssh/id_ed25519.pub +cat > /opt/APP_NAME/update-images.sh << 'EOF' +#!/bin/bash + +# Update images from private registry +set -e + +echo "Updating images from private registry..." + +# Pull latest images +docker-compose pull + +# Show updated images +echo "Updated images:" +docker images | grep APP_NAME + +echo "Update complete!" +echo "Run './deploy.sh' to deploy with new images" +EOF + +chmod +x /opt/APP_NAME/update-images.sh ``` -**Important**: Copy this public key - you'll need it for the CI/CD server setup. - -#### 3.2 Add CI/CD Server's Public Key - -```bash -echo "CI_CD_PUBLIC_KEY_HERE" >> ~/.ssh/authorized_keys -sed -i 's/YOUR_CI_CD_IP/YOUR_ACTUAL_CI_CD_IP/g' ~/.ssh/config -``` - -**What this does**: Adds the CI/CD server's public key to allow secure communication. - -**Note**: Replace `CI_CD_PUBLIC_KEY_HERE` with the actual public key from your CI/CD server. - -### Step 4: Update Application Configuration for CI/CD - -#### 4.1 Update Environment Variables - -```bash -cd /opt/APP_NAME -nano .env -``` - -**Required changes**: -- Replace `your-registry-url:port` with `YOUR_CI_HOST_IP:5000` -- Replace `your_secure_password_here` with a strong database password -- Update `DATABASE_URL` with the same password - -**Example**: -```bash -# Change this line: -REGISTRY=your-registry-url:port - -# To this (replace with your actual CI/CD IP): -REGISTRY=192.168.1.100:5000 -``` - -### Step 5: Test CI/CD Integration - -#### 5.1 Test SSH Connection - -```bash -ssh ci-cd 'echo Connection successful' -``` - -**Expected output**: `Connection successful`. - -#### 5.2 Test Registry Connection - -```bash -curl http://YOUR_ACTUAL_CI_IP:5000/v2/_catalog -``` - -**Expected output**: `{"repositories":[]}` or a list of available images. - -### Step 6: Configure Forgejo Repository Secrets - -Go to your Forgejo repository → Settings → Secrets and Variables → Actions, and add: - -- `CI_HOST`: Your CI/CD Linode IP address -- `PROD_HOST`: Your production Linode IP -- `PROD_USER`: SSH username for production server (should be `SERVICE_USER`) -- `PROD_SSH_KEY`: SSH private key for deployment (from CI/CD server) - -### Step 7: Test Automated Deployment - -#### 7.1 Push Code Changes - -Make a small change to your code and push to trigger the CI/CD pipeline: - -```bash -# In your local repository -echo "# Test deployment" >> README.md -git add README.md -git commit -m "Test CI/CD pipeline" -git push -``` - -#### 7.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 - -#### 7.3 Verify Deployment - -After successful pipeline execution: - -```bash -# 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 -``` +**What this does**: Creates a script to update images from your private registry. --- @@ -736,6 +820,7 @@ curl -I https://your-domain.com ### Step 1: Test Application Access +**If you have a domain:** ```bash # Test HTTP redirect to HTTPS curl -I http://your-domain.com @@ -747,9 +832,18 @@ curl -I https://your-domain.com curl -I https://your-domain.com/api/health ``` +**If you don't have a domain (IP access only):** +```bash +# Test HTTP access via IP +curl -I http://YOUR_PRODUCTION_IP + +# Test application endpoints +curl -I http://YOUR_PRODUCTION_IP/api/health +``` + **Expected output**: -- HTTP: 301 redirect to HTTPS -- HTTPS: 200 OK response +- Domain users: HTTP redirect to HTTPS, HTTPS 200 OK response +- IP users: HTTP 200 OK response - API: Application-specific response ### Step 2: Test Monitoring @@ -770,7 +864,7 @@ cd /opt/APP_NAME **Expected output**: Backup completion message with file locations. -### Step 4: Test Manual Deployment (if not using CI/CD) +### Step 4: Test Manual Deployment ```bash cd /opt/APP_NAME @@ -779,122 +873,6 @@ cd /opt/APP_NAME **Expected output**: Pull progress messages, image download, and deployment completion. -**Alternative manual process**: -```bash -# Pull images manually -docker pull your-remote-registry.com:port/APP_NAME-backend:latest -docker pull your-remote-registry.com:port/APP_NAME-frontend:latest - -# Deploy application -cd /opt/APP_NAME -./deploy.sh -``` - -### Step 5: Manual Image Management (for non-CI/CD deployments) - -If you're not using CI/CD, you'll need to pull and manage Docker images from your remote registry: - -#### 5.1 Configure Registry Authentication - -```bash -# Login to your remote registry -docker login your-remote-registry.com:port - -# For GitHub Container Registry -docker login ghcr.io -u YOUR_GITHUB_USERNAME -p YOUR_GITHUB_TOKEN - -# For GitLab Container Registry -docker login registry.gitlab.com -u YOUR_GITLAB_USERNAME -p YOUR_GITLAB_TOKEN - -# For AWS ECR -aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com -``` - -#### 5.2 Pull Images from Remote Registry - -```bash -# Pull backend image -docker pull your-remote-registry.com:port/APP_NAME-backend:latest - -# Pull frontend image -docker pull your-remote-registry.com:port/APP_NAME-frontend:latest - -# Verify images were pulled -docker images | grep APP_NAME -``` - -#### 5.3 Update Application Configuration - -```bash -# Update docker-compose.yml to use your remote registry images -cd /opt/APP_NAME -nano docker-compose.yml - -# Example configuration: -# image: your-remote-registry.com:port/APP_NAME-backend:latest -# image: your-remote-registry.com:port/APP_NAME-frontend:latest -``` - -#### 5.4 Create Manual Pull and Deploy Script - -```bash -cat > /opt/APP_NAME/pull-and-deploy.sh << 'EOF' -#!/bin/bash - -# Manual pull and deploy script -set -e - -echo "Pulling APP_NAME images from remote registry..." - -# Pull backend -docker pull your-remote-registry.com:port/APP_NAME-backend:latest - -# Pull frontend -docker pull your-remote-registry.com:port/APP_NAME-frontend:latest - -# Show pulled images -echo "Pulled images:" -docker images | grep APP_NAME - -# Deploy application -cd /opt/APP_NAME -./deploy.sh - -echo "Pull and deploy complete!" -EOF - -chmod +x /opt/APP_NAME/pull-and-deploy.sh -``` - -**What this does**: Creates a script to pull images from your remote registry and deploy the application. - -#### 5.5 Create Image Update Script - -```bash -cat > /opt/APP_NAME/update-images.sh << 'EOF' -#!/bin/bash - -# Update images from remote registry -set -e - -echo "Updating images from remote registry..." - -# Pull latest images -docker-compose pull - -# Show updated images -echo "Updated images:" -docker images | grep APP_NAME - -echo "Update complete!" -echo "Run './deploy.sh' to deploy with new images" -EOF - -chmod +x /opt/APP_NAME/update-images.sh -``` - -**What this does**: Creates a script to update images from your remote registry. - --- ## Troubleshooting @@ -907,7 +885,7 @@ chmod +x /opt/APP_NAME/update-images.sh newgrp docker ``` -2. **SSL certificate issues**: +2. **SSL certificate issues** (domain users only): ```bash sudo certbot certificates sudo certbot renew --dry-run @@ -919,64 +897,54 @@ chmod +x /opt/APP_NAME/update-images.sh docker-compose logs ``` -4. **SSH connection failed** (CI/CD integration only): - ```bash - ssh -v ci-cd 'echo test' - ``` +### Private Registry Issues -5. **Registry connection failed** (CI/CD integration only): - ```bash - curl -v http://YOUR_CI_HOST_IP:5000/v2/_catalog - ``` - -### Manual Deployment Issues - -6. **Registry authentication fails**: +4. **Registry authentication fails**: ```bash # Check if you're logged in - docker login your-remote-registry.com:port - - # For GitHub Container Registry - docker login ghcr.io -u YOUR_GITHUB_USERNAME -p YOUR_GITHUB_TOKEN - - # For GitLab Container Registry - docker login registry.gitlab.com -u YOUR_GITLAB_USERNAME -p YOUR_GITLAB_TOKEN + docker login your-registry.com:port -u YOUR_REGISTRY_USERNAME -p YOUR_REGISTRY_PASSWORD # Check authentication status cat ~/.docker/config.json + + # Test registry connectivity + curl -v https://your-registry.com:port/v2/_catalog ``` -7. **Images not found in remote registry**: +5. **Images not found in private registry**: ```bash # Check if images exist in your registry - docker pull your-remote-registry.com:port/APP_NAME-backend:latest + docker pull your-registry.com:port/APP_NAME-backend:latest # List local images docker images | grep APP_NAME # Check registry connectivity - curl -v https://your-remote-registry.com:port/v2/_catalog + curl -v https://your-registry.com:port/v2/_catalog - # For GitHub Container Registry - curl -H "Authorization: Bearer YOUR_GITHUB_TOKEN" https://ghcr.io/v2/_catalog + # List available repositories + curl -u YOUR_REGISTRY_USERNAME:YOUR_REGISTRY_PASSWORD https://your-registry.com:port/v2/_catalog ``` -8. **Network connectivity issues**: +6. **Network connectivity issues**: ```bash # Test basic connectivity - ping your-remote-registry.com + ping your-registry.com # Test port connectivity - telnet your-remote-registry.com port + telnet your-registry.com port # Check DNS resolution - nslookup your-remote-registry.com + nslookup your-registry.com # Test HTTPS connectivity - curl -I https://your-remote-registry.com:port/v2/ + curl -I https://your-registry.com:port/v2/ + + # Test HTTP connectivity (if registry uses HTTP) + curl -I http://your-registry.com:port/v2/ ``` -9. **Application configuration issues**: +7. **Application configuration issues**: ```bash # Check environment variables cd /opt/APP_NAME @@ -989,20 +957,20 @@ chmod +x /opt/APP_NAME/update-images.sh docker-compose logs --tail=50 ``` -10. **Manual pull and deploy process issues**: - ```bash - # Check if pull script exists - ls -la /opt/APP_NAME/pull-and-deploy.sh - - # Run pull with verbose output - bash -x /opt/APP_NAME/pull-and-deploy.sh - - # Check for pull errors - docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.CreatedAt}}" - - # Verify docker-compose.yml references correct registry - grep -A 5 "image:" /opt/APP_NAME/docker-compose.yml - ``` +8. **Manual pull and deploy process issues**: + ```bash + # Check if pull script exists + ls -la /opt/APP_NAME/pull-and-deploy.sh + + # Run pull with verbose output + bash -x /opt/APP_NAME/pull-and-deploy.sh + + # Check for pull errors + docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.CreatedAt}}" + + # Verify docker-compose.yml references correct registry + grep -A 5 "image:" /opt/APP_NAME/docker-compose.yml + ``` ### Useful Commands @@ -1019,10 +987,11 @@ chmod +x /opt/APP_NAME/update-images.sh 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 +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 private registry access ### Monitoring and Maintenance @@ -1044,27 +1013,35 @@ cd /opt/APP_NAME #### Monthly Maintenance 1. **Review security**: Check firewall rules and fail2ban status -2. **Update certificates**: Ensure SSL certificates are valid +2. **Update certificates**: Ensure SSL certificates are valid (domain users only) 3. **Clean up old images**: Remove unused Docker images 4. **Review monitoring**: Check application performance and logs +5. **Verify registry access**: Test private registry connectivity and authentication --- ## Summary -Your Production Linode is now ready to host your application! The setup includes: +Your Production Linode is now ready for manual deployment from a private registry! The setup includes: -### Basic Features (All Deployments) -- **Docker-based application deployment** with SSL/TLS +### Basic Features +- **Docker-based application deployment** - **Nginx reverse proxy** with security headers - **Automated backups and monitoring** scripts - **Firewall and fail2ban protection** for security -- **Manual image management** tools and scripts -### CI/CD Integration Features (Optional) -- **Secure SSH communication** with CI/CD server -- **Automated deployment** from CI/CD pipeline -- **Registry connection** for image pulling -- **Forgejo Actions integration** +### Optional Features (Domain Users) +- **SSL/TLS certificate management** with Let's Encrypt +- **HTTPS redirect** for secure web access -For ongoing maintenance and troubleshooting, refer to the troubleshooting section and monitoring scripts provided in this guide. \ No newline at end of file +### Private Registry Features +- **Secure registry authentication** with username/password +- **Manual image pulling and deployment** tools +- **Image update and rollback** capabilities +- **Deployment verification and testing** scripts + +### Access Methods +- **Domain users**: Access via `https://your-domain.com` +- **IP-only users**: Access via `http://YOUR_PRODUCTION_IP` + +For ongoing maintenance and troubleshooting, refer to the troubleshooting section and monitoring scripts provided in this guide. \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index b52b87a..4272fe0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,8 +6,8 @@ services: container_name: sharenet-postgres restart: unless-stopped environment: - POSTGRES_DB: sharenet - POSTGRES_USER: sharenet + POSTGRES_DB: ${POSTGRES_DB:-sharenet} + POSTGRES_USER: ${POSTGRES_USER:-sharenet} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-changeme} volumes: - postgres_data:/var/lib/postgresql/data @@ -15,7 +15,7 @@ services: ports: - "5432:5432" healthcheck: - test: ["CMD-SHELL", "pg_isready -U sharenet"] + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-sharenet}"] interval: 30s timeout: 10s retries: 3 @@ -27,7 +27,7 @@ services: container_name: sharenet-backend restart: unless-stopped environment: - DATABASE_URL: postgresql://sharenet:${POSTGRES_PASSWORD:-changeme}@postgres:5432/sharenet + DATABASE_URL: postgresql://${POSTGRES_USER:-sharenet}:${POSTGRES_PASSWORD:-changeme}@postgres:5432/${POSTGRES_DB:-sharenet} RUST_LOG: info RUST_BACKTRACE: 1 ports: diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 4bc2d08..435b837 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -52,8 +52,9 @@ http { server_name _; # SSL configuration - ssl_certificate /etc/nginx/ssl/cert.pem; - ssl_certificate_key /etc/nginx/ssl/key.pem; + # These paths match Let's Encrypt certificate files copied in the CI/CD setup guide + ssl_certificate /etc/nginx/ssl/fullchain.pem; + ssl_certificate_key /etc/nginx/ssl/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off;