diff --git a/CI_CD_PIPELINE_SETUP_GUIDE.md b/CI_CD_PIPELINE_SETUP_GUIDE.md index 3276cab..dd4492f 100644 --- a/CI_CD_PIPELINE_SETUP_GUIDE.md +++ b/CI_CD_PIPELINE_SETUP_GUIDE.md @@ -51,11 +51,10 @@ This guide covers setting up a complete Continuous Integration/Continuous Deploy - 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 0-9) +1. **Set up CI/CD Linode** (Steps 0-8) 2. **Set up Production Linode** (Steps 10-16) 3. **Set up Forgejo repository secrets** (Step 17) 4. **Test the complete pipeline** (Step 18) @@ -74,7 +73,6 @@ This guide covers setting up a complete Continuous Integration/Continuous Deploy ### Production Linode Features - Podman-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 @@ -136,7 +134,6 @@ This guide assumes you have already: 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 @@ -146,7 +143,7 @@ Before proceeding with the setup, you need to establish initial SSH access to bo 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) +- **Production Linode**: `YOUR_PRODUCTION_IP` (IP address for SSH and web access) #### 0.2 Test Initial SSH Access @@ -182,15 +179,14 @@ Before proceeding, decide on: 3. **Application Name**: Choose a name for your application (e.g., `sharenet`) - 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 `ci-service` as CI service account, `ci-deploy` as CI deployment user, and `sharenet` as application name: - Replace all `CI_SERVICE_USER` with `ci-service` - Replace all `CI_DEPLOY_USER` with `ci-deploy` - Replace all `APP_NAME` with `sharenet` - - If you have a domain `example.com`, replace `your-domain.com` with `example.com` + **Security Model**: - **CI Service Account (`CI_SERVICE_USER`)**: Runs CI pipeline and Docker operations, no sudo access @@ -766,13 +762,7 @@ sudo chmod 755 /var/lib/registry/data # Registry data #### 4.2.4 Generate TLS Certificate and Install for Podman -**Choose one of the following options based on whether you have a domain name:** - ---- - -#### **Option A: Self-Signed Certificate (No Domain Required)** - -**Perform all of these steps if you do NOT have a domain name:** +**Generate Self-Signed Certificate:** ```bash # 1. Generate self-signed certificate with proper CA chain using FHS-compliant structure @@ -830,80 +820,11 @@ sudo update-ca-certificates --- -#### **Option B: Let's Encrypt Certificate (Domain Required)** - -**Perform all of these steps if you DO have a domain name:** - -```bash -# 1. Generate Let's Encrypt certificate -sudo apt update -sudo apt install -y certbot python3-certbot-nginx -sudo certbot certonly --standalone \ - --email your-email@example.com \ - --agree-tos \ - --no-eff-email \ - -d YOUR_DOMAIN_NAME -sudo certbot certificates -sudo cp /etc/letsencrypt/live/YOUR_DOMAIN_NAME/fullchain.pem /etc/registry/certs/registry.crt -sudo cp /etc/letsencrypt/live/YOUR_DOMAIN_NAME/privkey.pem /etc/registry/certs/registry.key -sudo chown CI_SERVICE_USER:CI_SERVICE_USER /etc/registry/certs/registry.crt /etc/registry/certs/registry.key -sudo chmod 644 /etc/registry/certs/registry.crt -sudo chmod 600 /etc/registry/certs/registry.key - -# 2. Install certificate in system trust store (for curl, wget, etc.) -# Note: Let's Encrypt certificates are already trusted by the system, but we install for consistency -sudo cp /etc/registry/certs/registry.crt /usr/local/share/ca-certificates/registry-ca.crt -sudo update-ca-certificates -``` - ---- - **Note:** -- For **Option A**: Replace `YOUR_ACTUAL_IP_ADDRESS` with your server's IP address. -- For **Option B**: Replace `YOUR_DOMAIN_NAME` with your domain name and `your-email@example.com` with your email address. +- Replace `YOUR_ACTUAL_IP_ADDRESS` with your server's IP address. --- -**After completing the steps for your chosen option, continue with Step 4.5 (Set Up Systemd Service for Docker Registry v2).** - -#### 4.3 Install Certificate for Podman (Option B Only) - -**Important**: This step adds the Let's Encrypt certificate to the system trust store. Since Let's Encrypt is a trusted CA, system tools will automatically trust this certificate. - -```bash -# Install certificate in system trust store (for curl, wget, etc.) -sudo cp /etc/registry/certs/registry.crt /usr/local/share/ca-certificates/registry-ca.crt -sudo update-ca-certificates - -# Verify certificate installation -if [ -f "/usr/local/share/ca-certificates/registry-ca.crt" ]; then - echo "✅ Let's Encrypt certificate installed in system trust store" -else - echo "❌ Failed to install certificate in system trust store" - exit 1 -fi - -echo "Certificate installation completed successfully!" -echo "System tools can now connect to the registry securely using your domain name" -``` - -#### 4.4 Set Up Automatic Certificate Renewal (Option B Only) - -**Important**: Let's Encrypt certificates expire after 90 days, so we need to set up automatic renewal. - -```bash -# Test automatic renewal -sudo certbot renew --dry-run - -# Set up automatic renewal cron job -sudo crontab -e -# Add this line to renew certificates twice daily (Let's Encrypt allows renewal 30 days before expiry): -# 0 12,18 * * * /usr/bin/certbot renew --quiet --post-hook "cp /etc/letsencrypt/live/YOUR_DOMAIN_NAME/fullchain.pem /etc/registry/certs/registry.crt && cp /etc/letsencrypt/live/YOUR_DOMAIN_NAME/privkey.pem /etc/registry/certs/registry.key && chown CI_SERVICE_USER:CI_SERVICE_USER /etc/registry/certs/registry.* && chmod 644 /etc/registry/certs/registry.crt && chmod 600 /etc/registry/certs/registry.key && cp /etc/registry/certs/registry.crt /usr/local/share/ca-certificates/registry-ca.crt && update-ca-certificates && systemctl restart docker-registry.service" - -echo "Automatic certificate renewal configured!" -echo "Certificates will be renewed automatically and the registry service will be restarted" -``` - #### 4.5 Set Up Systemd Service for Docker Registry v2 ```bash @@ -1026,52 +947,17 @@ env PODMAN_ROOT=/var/tmp/podman-$(id -u)/root PODMAN_RUNROOT=/run/user/$(id -u)/ exit ``` -**For Option B (Let's Encrypt certificates):** -```bash -# Switch to CI_SERVICE_USER for testing (CI_SERVICE_USER runs CI pipeline and Docker operations) -sudo su - CI_SERVICE_USER - -# Navigate to the application directory -cd /opt/APP_NAME - -# Test Podman login and push (now using Let's Encrypt certificate with domain) -echo "your-secure-registry-password" | env PODMAN_ROOT=/var/tmp/podman-$(id -u)/root PODMAN_RUNROOT=/run/user/$(id -u)/podman-run PODMAN_TMPDIR=/var/tmp/podman-$(id -u)/tmp XDG_DATA_HOME=/var/tmp/podman-$(id -u)/xdg-data XDG_CONFIG_HOME=/var/tmp/podman-$(id -u)/xdg-config podman login YOUR_DOMAIN_NAME -u registry-user --password-stdin - -# Create and push test image -echo "FROM alpine:latest" > /tmp/test.Dockerfile -env PODMAN_ROOT=/var/tmp/podman-$(id -u)/root PODMAN_RUNROOT=/run/user/$(id -u)/podman-run PODMAN_TMPDIR=/var/tmp/podman-$(id -u)/tmp XDG_DATA_HOME=/var/tmp/podman-$(id -u)/xdg-data XDG_CONFIG_HOME=/var/tmp/podman-$(id -u)/xdg-config podman build -f /tmp/test.Dockerfile -t YOUR_DOMAIN_NAME/APP_NAME/test:latest /tmp -env PODMAN_ROOT=/var/tmp/podman-$(id -u)/root PODMAN_RUNROOT=/run/user/$(id -u)/podman-run PODMAN_TMPDIR=/var/tmp/podman-$(id -u)/tmp XDG_DATA_HOME=/var/tmp/podman-$(id -u)/xdg-data XDG_CONFIG_HOME=/var/tmp/podman-$(id -u)/xdg-config podman push YOUR_DOMAIN_NAME/APP_NAME/test:latest - -# Test public pull (no authentication) -env PODMAN_ROOT=/var/tmp/podman-$(id -u)/root PODMAN_RUNROOT=/run/user/$(id -u)/podman-run PODMAN_TMPDIR=/var/tmp/podman-$(id -u)/tmp XDG_DATA_HOME=/var/tmp/podman-$(id -u)/xdg-data XDG_CONFIG_HOME=/var/tmp/podman-$(id -u)/xdg-config podman logout YOUR_DOMAIN_NAME -env PODMAN_ROOT=/var/tmp/podman-$(id -u)/root PODMAN_RUNROOT=/run/user/$(id -u)/podman-run PODMAN_TMPDIR=/var/tmp/podman-$(id -u)/tmp XDG_DATA_HOME=/var/tmp/podman-$(id -u)/xdg-data XDG_CONFIG_HOME=/var/tmp/podman-$(id -u)/xdg-config podman pull YOUR_DOMAIN_NAME/APP_NAME/test:latest - -# Test that unauthorized push is blocked -echo "FROM alpine:latest" > /tmp/unauthorized.Dockerfile -env PODMAN_ROOT=/var/tmp/podman-$(id -u)/root PODMAN_RUNROOT=/run/user/$(id -u)/podman-run PODMAN_TMPDIR=/var/tmp/podman-$(id -u)/tmp XDG_DATA_HOME=/var/tmp/podman-$(id -u)/xdg-data XDG_CONFIG_HOME=/var/tmp/podman-$(id -u)/xdg-config podman build -f /tmp/unauthorized.Dockerfile -t YOUR_DOMAIN_NAME/APP_NAME/unauthorized:latest /tmp -env PODMAN_ROOT=/var/tmp/podman-$(id -u)/root PODMAN_RUNROOT=/run/user/$(id -u)/podman-run PODMAN_TMPDIR=/var/tmp/podman-$(id -u)/tmp XDG_DATA_HOME=/var/tmp/podman-$(id -u)/xdg-data XDG_CONFIG_HOME=/var/tmp/podman-$(id -u)/xdg-config podman push YOUR_DOMAIN_NAME/APP_NAME/unauthorized:latest -# Expected: This should fail with authentication error - -# Clean up -env PODMAN_ROOT=/var/tmp/podman-$(id -u)/root PODMAN_RUNROOT=/run/user/$(id -u)/podman-run PODMAN_TMPDIR=/var/tmp/podman-$(id -u)/tmp XDG_DATA_HOME=/var/tmp/podman-$(id -u)/xdg-data XDG_CONFIG_HOME=/var/tmp/podman-$(id -u)/xdg-config podman rmi YOUR_DOMAIN_NAME/APP_NAME/test:latest -env PODMAN_ROOT=/var/tmp/podman-$(id -u)/root PODMAN_RUNROOT=/run/user/$(id -u)/podman-run PODMAN_TMPDIR=/var/tmp/podman-$(id -u)/tmp XDG_DATA_HOME=/var/tmp/podman-$(id -u)/xdg-data XDG_CONFIG_HOME=/var/tmp/podman-$(id -u)/xdg-config podman rmi YOUR_DOMAIN_NAME/APP_NAME/unauthorized:latest -exit -``` - -**Important**: For Option B, the registry configuration is handled via environment variables in the Docker Compose file, so no additional configuration changes are needed. **Expected behavior**: - ✅ Push requires authentication with `registry-user` credentials on port 4443 - ✅ Pull works without authentication (public read access) on port 443 - ✅ Unauthorized push is blocked on authenticated endpoint -- ✅ Registry accessible at `https://YOUR_ACTUAL_IP_ADDRESS:4443` for authenticated operations (Option A) -- ✅ Registry accessible at `https://YOUR_ACTUAL_IP_ADDRESS` for unauthenticated pulls (Option A) -- ✅ Registry accessible at `https://YOUR_DOMAIN_NAME` with valid Let's Encrypt certificate (Option B) -- ✅ Certificate automatically renews every 60 days (Option B only) +- ✅ Registry accessible at `https://YOUR_ACTUAL_IP_ADDRESS:4443` for authenticated operations +- ✅ Registry accessible at `https://YOUR_ACTUAL_IP_ADDRESS` for unauthenticated pulls - ✅ Proper FHS-compliant certificate structure with secure permissions -**Troubleshooting TLS Errors (Option A only):** +**Troubleshooting TLS Errors:** If you get a TLS error like `remote error: tls: internal error` when using self-signed certificates, verify the certificate installation and Docker configuration: @@ -1398,6 +1284,7 @@ podman exec ci-pip podman pull YOUR_CI_CD_IP/APP_NAME/test:latest # Clean up test images podman exec ci-pip podman rmi YOUR_CI_CD_IP:4443/APP_NAME/test:latest podman exec ci-pip podman rmi YOUR_CI_CD_IP/APP_NAME/test:latest +``` #### 6.3 Set Up Workspace Directory @@ -1460,7 +1347,6 @@ The Docker Registry setup now follows the Filesystem Hierarchy Standard (FHS) fo - **Log management**: Logs in `/var/log/nginx/` for centralized logging - **Configuration separation**: App configs in app directory, system data in system directories - **Policy enforcement**: Container policies for image signature verification -``` **What this does**: - **Configures certificate trust**: Properly sets up Docker Registry certificate trust in DinD @@ -1869,11 +1755,10 @@ exit ```bash # Install Podman and related tools -sudo apt install -y podman podman-compose +sudo apt install -y podman # Verify installation podman --version -podman-compose --version ``` #### 12.2 Configure Podman for Production Service Account @@ -2213,7 +2098,6 @@ sudo fail2ban-client status ```bash podman --version -podman-compose --version ``` #### 16.2 Test Docker Registry v2 Access @@ -2227,7 +2111,7 @@ podman pull YOUR_CI_CD_IP/APP_NAME/test:latest **Note**: Production uses unauthenticated pulls from the standard HTTPS port (443) for deployment operations. -**Note**: Application deployment testing will be done in Step 20 after the complete CI/CD pipeline is set up. +**Note**: Application deployment testing will be done in Step 19 after the complete CI/CD pipeline is set up. --- @@ -2249,9 +2133,7 @@ Go to your Forgejo repository and add these secrets in **Settings → Secrets an - `REGISTRY_PUSH_URL`: Docker Registry v2 URL for authenticated pushes (e.g., `YOUR_CI_CD_IP:4443`) - `REGISTRY_PULL_URL`: Docker Registry v2 URL for unauthenticated pulls (e.g., `YOUR_CI_CD_IP`) -**Optional Secrets (for domain users):** -- `DOMAIN`: Your domain name (e.g., `example.com`) -- `EMAIL`: Your email for SSL certificate notifications + **Note**: This setup uses custom Dockerfiles for testing environments with base images stored in Docker Registry. The CI pipeline automatically checks if base images exist in Docker Registry and pulls them from Docker Hub only when needed, eliminating rate limiting issues and providing better control over the testing environment. @@ -2317,40 +2199,14 @@ podman logs sharenet-production-pod-frontend #### 18.5 Test Application Functionality -1. **Frontend**: Visit your production URL (IP or domain) +1. **Frontend**: Visit your production URL (IP address) 2. **Backend API**: Test API endpoints 3. **Database**: Verify database connections 4. **Logs**: Check for any errors in application logs -### Step 19: Set Up SSL/TLS (Optional - Domain Users) +### Step 19: Final Verification -#### 19.1 Install SSL Certificate - -If you have a domain pointing to your Production Linode: - -```bash -# On Production Linode -sudo certbot --nginx -d your-domain.com - -# Verify certificate -sudo certbot certificates -``` - -#### 19.2 Configure Auto-Renewal - -```bash -# Test auto-renewal -sudo certbot renew --dry-run - -# Add to crontab for automatic renewal -sudo crontab -e -# Add this line: -# 0 12 * * * /usr/bin/certbot renew --quiet -``` - -### Step 20: Final Verification - -#### 20.1 Security Check +#### 19.1 Security Check ```bash # Check firewall status @@ -2363,7 +2219,7 @@ sudo systemctl status fail2ban sudo grep "PasswordAuthentication" /etc/ssh/sshd_config ``` -#### 20.2 Performance Check +#### 19.2 Performance Check ```bash # Check system resources @@ -2376,7 +2232,7 @@ df -h docker system df ``` -#### 20.3 Backup Verification +#### 19.3 Backup Verification ```bash # Test backup script @@ -2387,16 +2243,16 @@ cd /opt/APP_NAME ./scripts/backup.sh ``` -### Step 21: Documentation and Maintenance +### Step 20: Documentation and Maintenance -#### 21.1 Update Documentation +#### 20.1 Update Documentation 1. **Update README.md** with deployment information 2. **Document environment variables** and their purposes 3. **Create troubleshooting guide** for common issues 4. **Document backup and restore procedures** -#### 21.2 Set Up Monitoring Alerts +#### 20.2 Set Up Monitoring Alerts ```bash # Set up monitoring cron job @@ -2406,7 +2262,7 @@ cd /opt/APP_NAME tail -f /tmp/monitor.log ``` -#### 21.3 Regular Maintenance Tasks +#### 20.3 Regular Maintenance Tasks **Daily:** - Check application logs for errors @@ -2420,7 +2276,6 @@ tail -f /tmp/monitor.log **Monthly:** - Review and rotate logs -- Update SSL certificates - Review and update documentation --- @@ -2435,7 +2290,7 @@ You have successfully set up a complete CI/CD pipeline with: - ✅ **Health monitoring** and logging - ✅ **Backup and cleanup** automation - ✅ **Security hardening** with proper user separation -- ✅ **SSL/TLS support** for production (optional) +- ✅ **SSL/TLS support** with self-signed certificates - ✅ **Zero resource contention** between CI/CD and Docker Registry - ✅ **FHS-compliant directory structure** for better organization and security