Change to use nginx instead of Caddy for docker registry
Some checks are pending
CI/CD Pipeline (Fully Isolated DinD) / Run Tests (DinD) (push) Waiting to run
CI/CD Pipeline (Fully Isolated DinD) / Build and Push Docker Images (DinD) (push) Blocked by required conditions
CI/CD Pipeline (Fully Isolated DinD) / Deploy to Production (push) Blocked by required conditions

This commit is contained in:
continuist 2025-08-18 18:18:30 -04:00
parent bf41839b8c
commit 0b4fb89e77
5 changed files with 163 additions and 90 deletions

View file

@ -65,10 +65,10 @@ This guide covers setting up a complete Continuous Integration/Continuous Deploy
### CI/CD Linode Features ### CI/CD Linode Features
- Forgejo Actions runner for automated builds - Forgejo Actions runner for automated builds
- **Docker-in-Docker (DinD) container** for isolated CI operations - **Docker-in-Docker (DinD) container** for isolated CI operations
- Docker Registry with Caddy reverse proxy for image storage - Docker Registry with nginx reverse proxy for image storage
- **FHS-compliant directory structure** for data, certificates, and logs - **FHS-compliant directory structure** for data, certificates, and logs
- Unauthenticated pulls, authenticated pushes - Unauthenticated pulls, authenticated pushes
- Automatic HTTPS with Caddy - Automatic HTTPS with nginx
- Secure SSH communication with production - Secure SSH communication with production
- **Simplified cleanup** - just restart DinD container - **Simplified cleanup** - just restart DinD container
@ -654,9 +654,9 @@ sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo usermod -aG docker CI_SERVICE_USER sudo usermod -aG docker CI_SERVICE_USER
``` ```
### Step 5: Set Up Docker Registry with Caddy ### Step 5: Set Up Docker Registry with nginx
We'll set up a basic Docker Registry with Caddy as a reverse proxy, configured to allow unauthenticated pulls but require authentication for pushes. We'll set up a basic Docker Registry with nginx as a reverse proxy, configured to allow unauthenticated pulls but require authentication for pushes.
#### 5.1 Configure FHS-Compliant Registry Directories #### 5.1 Configure FHS-Compliant Registry Directories
@ -679,38 +679,34 @@ sudo chmod 755 /var/log/registry
# Navigate to the cloned application directory # Navigate to the cloned application directory
cd /opt/APP_NAME/registry cd /opt/APP_NAME/registry
# Update Caddyfile with your actual IP address # Update nginx.conf with your actual IP address
sudo sed -i "s/YOUR_CI_CD_IP/YOUR_ACTUAL_IP_ADDRESS/g" /opt/APP_NAME/registry/Caddyfile sudo sed -i "s/YOUR_CI_CD_IP/YOUR_ACTUAL_IP_ADDRESS/g" /opt/APP_NAME/registry/nginx.conf
# Update openssl.conf with your actual IP address and registry name # Update openssl.conf with your actual IP address and registry name
sudo sed -i "s/YOUR_CI_CD_IP/YOUR_ACTUAL_IP_ADDRESS/g" /opt/APP_NAME/registry/openssl.conf sudo sed -i "s/YOUR_CI_CD_IP/YOUR_ACTUAL_IP_ADDRESS/g" /opt/APP_NAME/registry/openssl.conf
sudo sed -i "s/YOUR_REGISTRY_NAME/APP_NAME-Registry/g" /opt/APP_NAME/registry/openssl.conf sudo sed -i "s/YOUR_REGISTRY_NAME/APP_NAME-Registry/g" /opt/APP_NAME/registry/openssl.conf
# Create FHS-compliant environment directory # Create FHS-compliant authentication directory
sudo mkdir -p /etc/registry/env sudo mkdir -p /etc/registry/auth
sudo chown CI_SERVICE_USER:CI_SERVICE_USER /etc/registry/env sudo chown CI_SERVICE_USER:CI_SERVICE_USER /etc/registry/auth
sudo chmod 755 /etc/registry/env sudo chmod 755 /etc/registry/auth
# Create secure environment file for registry authentication # Create htpasswd file for nginx authentication
# First, create a secure password hash
# Save this password somewhere safe # Save this password somewhere safe
REGISTRY_PASSWORD="your-secure-registry-password" REGISTRY_PASSWORD="your-secure-registry-password"
REGISTRY_PASSWORD_HASH=$(htpasswd -nbB registry-user "$REGISTRY_PASSWORD" | cut -d: -f2)
# Create the .env file in FHS-compliant location # Create htpasswd file in FHS-compliant location
sudo tee /etc/registry/env/.env > /dev/null <<EOF sudo htpasswd -cb /etc/registry/auth/.htpasswd registry-user "$REGISTRY_PASSWORD"
REGISTRY_PASSWORD_HASH=$REGISTRY_PASSWORD_HASH
EOF
# Set secure permissions on .env file (owner read/write only) # Set secure permissions on htpasswd file (owner read/write only)
sudo chown CI_SERVICE_USER:CI_SERVICE_USER /etc/registry/env/.env sudo chown CI_SERVICE_USER:CI_SERVICE_USER /etc/registry/auth/.htpasswd
sudo chmod 600 /etc/registry/env/.env sudo chmod 600 /etc/registry/auth/.htpasswd
# Set proper permissions for configuration files # Set proper permissions for configuration files
sudo chown CI_SERVICE_USER:CI_SERVICE_USER /opt/APP_NAME/registry/Caddyfile sudo chown CI_SERVICE_USER:CI_SERVICE_USER /opt/APP_NAME/registry/nginx.conf
sudo chown CI_SERVICE_USER:CI_SERVICE_USER /opt/APP_NAME/registry/openssl.conf sudo chown CI_SERVICE_USER:CI_SERVICE_USER /opt/APP_NAME/registry/openssl.conf
sudo chown CI_SERVICE_USER:CI_SERVICE_USER /opt/APP_NAME/registry/docker-compose.registry.yml sudo chown CI_SERVICE_USER:CI_SERVICE_USER /opt/APP_NAME/registry/docker-compose.registry.yml
sudo chmod 644 /opt/APP_NAME/registry/Caddyfile sudo chmod 644 /opt/APP_NAME/registry/nginx.conf
sudo chmod 644 /opt/APP_NAME/registry/openssl.conf sudo chmod 644 /opt/APP_NAME/registry/openssl.conf
sudo chmod 644 /opt/APP_NAME/registry/docker-compose.registry.yml sudo chmod 644 /opt/APP_NAME/registry/docker-compose.registry.yml
``` ```
@ -928,8 +924,8 @@ sudo systemctl status docker-registry.service
# Check that containers are running # Check that containers are running
sudo su - CI_SERVICE_USER -c "cd /opt/APP_NAME/registry && docker compose -f docker-compose.registry.yml ps" sudo su - CI_SERVICE_USER -c "cd /opt/APP_NAME/registry && docker compose -f docker-compose.registry.yml ps"
# Check Caddy logs # Check nginx logs
sudo su - CI_SERVICE_USER -c "cd /opt/APP_NAME/registry && docker compose -f docker-compose.registry.yml logs caddy" sudo su - CI_SERVICE_USER -c "cd /opt/APP_NAME/registry && docker compose -f docker-compose.registry.yml logs nginx"
# Check Registry logs # Check Registry logs
sudo su - CI_SERVICE_USER -c "cd /opt/APP_NAME/registry && docker compose -f docker-compose.registry.yml logs registry" sudo su - CI_SERVICE_USER -c "cd /opt/APP_NAME/registry && docker compose -f docker-compose.registry.yml logs registry"
@ -1031,8 +1027,8 @@ openssl verify -CAfile /etc/registry/certs/ca/ca.crt /etc/registry/certs/registr
# Test the certificate connection # Test the certificate connection
openssl s_client -connect YOUR_ACTUAL_IP_ADDRESS:4443 -servername YOUR_ACTUAL_IP_ADDRESS < /dev/null openssl s_client -connect YOUR_ACTUAL_IP_ADDRESS:4443 -servername YOUR_ACTUAL_IP_ADDRESS < /dev/null
# Verify Caddy is using the correct certificates # Verify nginx is using the correct certificates
docker exec caddy ls -la /etc/certs/ docker exec nginx ls -la /etc/registry/certs/
# If issues persist, restart Docker daemon to reload certificates # If issues persist, restart Docker daemon to reload certificates
sudo systemctl restart docker sudo systemctl restart docker
@ -1383,7 +1379,7 @@ The Docker Registry setup now follows the Filesystem Hierarchy Standard (FHS) fo
**Application Files** (in `/opt/APP_NAME/registry/`): **Application Files** (in `/opt/APP_NAME/registry/`):
- `docker-compose.registry.yml` - Docker Compose configuration from project repository - `docker-compose.registry.yml` - Docker Compose configuration from project repository
- `Caddyfile` - Caddy reverse proxy configuration from project repository - `nginx.conf` - nginx reverse proxy configuration from project repository
- `openssl.conf` - OpenSSL configuration for certificate generation from project repository - `openssl.conf` - OpenSSL configuration for certificate generation from project repository
- `docker-registry.service` - Systemd service file for Docker Registry - `docker-registry.service` - Systemd service file for Docker Registry
@ -1394,10 +1390,9 @@ The Docker Registry setup now follows the Filesystem Hierarchy Standard (FHS) fo
- `/etc/registry/certs/ca/` - CA certificates (mode 644) - `/etc/registry/certs/ca/` - CA certificates (mode 644)
- `/etc/registry/certs/requests/` - Certificate requests and configs (mode 644) - `/etc/registry/certs/requests/` - Certificate requests and configs (mode 644)
- `/etc/registry/certs/registry.crt` - Server certificate (mode 644) - `/etc/registry/certs/registry.crt` - Server certificate (mode 644)
- `/etc/registry/env/` - Environment variables and secrets: - `/etc/registry/auth/.htpasswd` - nginx authentication file (mode 600)
- `/etc/registry/env/.env` - Registry authentication secrets (mode 600)
- `/etc/systemd/system/docker-registry.service` - Systemd service configuration - `/etc/systemd/system/docker-registry.service` - Systemd service configuration
- `/var/log/registry/` - Registry and Caddy logs - `/var/log/registry/` - Registry and nginx logs
**Benefits of FHS Compliance**: **Benefits of FHS Compliance**:
- **Data persistence**: Registry data stored in `/var/lib/registry/data/` survives container restarts - **Data persistence**: Registry data stored in `/var/lib/registry/data/` survives container restarts
@ -1538,7 +1533,7 @@ sudo ufw --force enable
sudo ufw default deny incoming sudo ufw default deny incoming
sudo ufw default allow outgoing sudo ufw default allow outgoing
sudo ufw allow ssh sudo ufw allow ssh
sudo ufw allow 443/tcp # Docker Registry via Caddy (public read access) sudo ufw allow 443/tcp # Docker Registry via nginx (public read access)
``` ```
**Security Model**: **Security Model**:
@ -1852,7 +1847,7 @@ ls -la /opt/APP_NAME
### Step 13: Configure Docker for Docker Registry Access ### Step 13: Configure Docker for Docker Registry Access
**Important**: The Production Linode needs to be able to pull Docker images from the Docker Registry on the CI/CD Linode. Since we're using Caddy with automatic HTTPS, no additional certificate configuration is needed. **Important**: The Production Linode needs to be able to pull Docker images from the Docker Registry on the CI/CD Linode. Since we're using nginx with automatic HTTPS, no additional certificate configuration is needed.
```bash ```bash
# Change to the PROD_SERVICE_USER # Change to the PROD_SERVICE_USER
@ -1873,7 +1868,7 @@ exit
**What this does**: **What this does**:
- **Tests Docker Registry access**: Verifies that Docker can successfully pull images from the Docker Registry - **Tests Docker Registry access**: Verifies that Docker can successfully pull images from the Docker Registry
- **No certificate configuration needed**: Caddy handles HTTPS automatically - **No certificate configuration needed**: nginx handles HTTPS automatically
- **Simple setup**: No complex certificate management required - **Simple setup**: No complex certificate management required
### Step 14: Set Up Forgejo Runner for Production Deployment ### Step 14: Set Up Forgejo Runner for Production Deployment
@ -2195,7 +2190,7 @@ Go to your Forgejo repository and add these secrets in **Settings → Secrets an
- `APP_NAME`: Your application name (e.g., `sharenet`) - `APP_NAME`: Your application name (e.g., `sharenet`)
- `POSTGRES_PASSWORD`: A strong password for the PostgreSQL database - `POSTGRES_PASSWORD`: A strong password for the PostgreSQL database
- `REGISTRY_USER`: Docker Registry username for CI operations (e.g., `registry-user`) - `REGISTRY_USER`: Docker Registry username for CI operations (e.g., `registry-user`)
- `REGISTRY_PASSWORD`: Docker Registry password for CI operations (the password you set in the Caddyfile, default: `your-secure-registry-password`) - `REGISTRY_PASSWORD`: Docker Registry password for CI operations (the password you set in the nginx configuration, default: `your-secure-registry-password`)
- `REGISTRY_PUSH_URL`: Docker Registry URL for authenticated pushes (e.g., `YOUR_CI_CD_IP:4443`) - `REGISTRY_PUSH_URL`: Docker Registry URL for authenticated pushes (e.g., `YOUR_CI_CD_IP:4443`)
- `REGISTRY_PULL_URL`: Docker Registry URL for unauthenticated pulls (e.g., `YOUR_CI_CD_IP`) - `REGISTRY_PULL_URL`: Docker Registry URL for unauthenticated pulls (e.g., `YOUR_CI_CD_IP`)
@ -2402,17 +2397,17 @@ sudo rm -rf /opt/APP_NAME/registry/certs/requests/openssl.conf
# Note: DO NOT remove these files as they are needed for operation: # Note: DO NOT remove these files as they are needed for operation:
# - /opt/APP_NAME/registry/docker-compose.registry.yml # - /opt/APP_NAME/registry/docker-compose.registry.yml
# - /opt/APP_NAME/registry/Caddyfile # - /opt/APP_NAME/registry/nginx.conf
# - /opt/APP_NAME/registry/docker-registry.service # - /opt/APP_NAME/registry/docker-registry.service
# - /etc/registry/env/.env (contains the actual secrets) # - /etc/registry/auth/.htpasswd (contains the actual secrets)
# - /etc/systemd/system/docker-registry.service # - /etc/systemd/system/docker-registry.service
``` ```
**Security Note**: The `.env` file in `/etc/registry/env/.env` contains sensitive authentication data and should be: **Security Note**: The `.htpasswd` file in `/etc/registry/auth/.htpasswd` contains sensitive authentication data and should be:
- **Backed up securely** if needed for disaster recovery - **Backed up securely** if needed for disaster recovery
- **Never committed to version control** - **Never committed to version control**
- **Protected with proper permissions** (600 - owner read/write only) - **Protected with proper permissions** (600 - owner read/write only)
- **Rotated regularly** by updating the password and regenerating the hash - **Rotated regularly** by updating the password and regenerating the htpasswd file
### Step 8.6 CI/CD Workflow Summary Table ### Step 8.6 CI/CD Workflow Summary Table

View file

@ -1,38 +0,0 @@
# Unauthenticated pulls on 443 (GET requests only)
:443 {
tls /etc/registry/certs/registry.crt /etc/registry/certs/private/registry.key
log
# Block all write operations explicitly
@writes method PUT POST PATCH DELETE
respond @writes "Method Not Allowed" 405
# Allow all GET requests to v2 API (Docker Registry itself will handle security)
reverse_proxy /v2/* registry:5000
}
# Auth-required pushes on 4443
:4443 {
tls /etc/registry/certs/registry.crt /etc/registry/certs/private/registry.key
log
# require auth on writes
@writes method PUT POST PATCH DELETE
basic_auth @writes {
registry-user {env.REGISTRY_PASSWORD_HASH}
}
# also require auth on the /v2/ ping so Docker sends creds
@v2ping {
path /v2/
method GET
}
basic_auth @v2ping {
registry-user {env.REGISTRY_PASSWORD_HASH}
}
reverse_proxy /v2/* registry:5000
}
# TODO: Add Option B: Let's Encrypt certificates (Domain name)

View file

@ -4,8 +4,8 @@ This folder contains the configuration files for the Docker Registry setup used
## Files ## Files
- **`docker-compose.registry.yml`**: Docker Compose configuration for the registry and Caddy reverse proxy - **`docker-compose.registry.yml`**: Docker Compose configuration for the registry and nginx reverse proxy
- **`Caddyfile`**: Caddy configuration for HTTPS and authentication - **`nginx.conf`**: nginx configuration for HTTPS and authentication
- **`docker-registry.service`**: Systemd service file for Docker Registry - **`docker-registry.service`**: Systemd service file for Docker Registry
- **`README.md`**: This documentation file - **`README.md`**: This documentation file
@ -13,9 +13,9 @@ This folder contains the configuration files for the Docker Registry setup used
The registry setup uses: The registry setup uses:
- **Docker Registry**: Basic registry for storing Docker images - **Docker Registry**: Basic registry for storing Docker images
- **Caddy**: Reverse proxy with automatic HTTPS and authentication - **nginx**: Reverse proxy with automatic HTTPS and authentication
- **Environment Variables**: For authentication credentials and registry configuration - **Environment Variables**: For authentication credentials and registry configuration
- **Service User**: The registry and Caddy services run as the existing `CI_SERVICE_USER` (not a separate registry user) - **Service User**: The registry and nginx services run as the existing `CI_SERVICE_USER` (not a separate registry user)
## Authentication Model ## Authentication Model
@ -40,8 +40,8 @@ The registry setup uses:
The setup is configured through: The setup is configured through:
1. **Environment Variables**: Stored in `.env` file (created during setup) for authentication 1. **Environment Variables**: Stored in `.env` file (created during setup) for authentication
2. **Docker Compose Environment**: Registry configuration via environment variables 2. **Docker Compose Environment**: Registry configuration via environment variables
3. **Caddyfile**: Handles HTTPS and authentication 3. **nginx.conf**: Handles HTTPS and authentication
4. **Docker Compose**: Orchestrates the registry and Caddy services 4. **Docker Compose**: Orchestrates the registry and nginx services
5. **Systemd Service**: Manages the Docker Registry service lifecycle 5. **Systemd Service**: Manages the Docker Registry service lifecycle
6. **User/Permissions**: All files and services are owned and run by `CI_SERVICE_USER` for consistency and security 6. **User/Permissions**: All files and services are owned and run by `CI_SERVICE_USER` for consistency and security
@ -51,8 +51,8 @@ The registry is automatically set up during the CI/CD pipeline setup process. Th
## Security ## Security
- Authentication is handled by Caddy using environment variables - Authentication is handled by nginx using htpasswd file
- HTTPS is automatically managed by Caddy - HTTPS is automatically managed by nginx
- Registry data is persisted in Docker volumes - Registry data is persisted in Docker volumes
- Environment file contains sensitive credentials and should be properly secured - Environment file contains sensitive credentials and should be properly secured
- All files and services are owned by `CI_SERVICE_USER` (not a separate registry user) - All files and services are owned by `CI_SERVICE_USER` (not a separate registry user)

View file

@ -14,9 +14,9 @@ services:
expose: expose:
- "5000" # internal only, not published - "5000" # internal only, not published
caddy: nginx:
image: caddy:2 image: nginx:alpine
container_name: caddy container_name: nginx
restart: unless-stopped restart: unless-stopped
depends_on: depends_on:
- registry - registry
@ -25,7 +25,8 @@ services:
- "4443:4443" - "4443:4443"
# deliberately no "80:80" no HTTP # deliberately no "80:80" no HTTP
volumes: volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro - ./nginx.conf:/etc/nginx/nginx.conf:ro
- /etc/registry/certs:/etc/registry/certs:ro - /etc/registry/certs:/etc/registry/certs:ro
environment: - /etc/registry/auth/.htpasswd:/etc/nginx/.htpasswd:ro
- REGISTRY_PASSWORD_HASH=${REGISTRY_PASSWORD_HASH} - /var/log/nginx:/var/log/nginx

115
registry/nginx.conf Normal file
View file

@ -0,0 +1,115 @@
# Docker Registry Nginx Configuration
# Port 443: Unauthenticated pulls (GET requests only)
# Port 4443: Authenticated operations (login, logout, push, delete, etc.)
# Upstream Docker Registry
upstream registry {
server registry:5000;
}
# HTTP server for unauthenticated pulls on port 443
server {
listen 443 ssl http2;
server_name _;
# SSL Configuration
ssl_certificate /etc/registry/certs/registry.crt;
ssl_certificate_key /etc/registry/certs/private/registry.key;
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;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# 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=31536000; includeSubDomains" always;
# Block all write operations explicitly
if ($request_method !~ ^(GET|HEAD)$) {
return 405 "Method Not Allowed";
}
# Allow all GET requests to v2 API (Docker Registry itself will handle security)
location /v2/ {
proxy_pass http://registry;
proxy_set_header Host $http_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;
proxy_read_timeout 900;
proxy_connect_timeout 60;
proxy_send_timeout 60;
}
# Health check endpoint
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
# Default location - deny all
location / {
return 404;
}
# Logging
access_log /var/log/nginx/registry_access.log;
error_log /var/log/nginx/registry_error.log;
}
# HTTPS server for authenticated operations on port 4443
server {
listen 4443 ssl http2;
server_name _;
# SSL Configuration
ssl_certificate /etc/registry/certs/registry.crt;
ssl_certificate_key /etc/registry/certs/private/registry.key;
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;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# 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=31536000; includeSubDomains" always;
# Basic authentication for write operations
location ~ ^/v2/.*$ {
# Require auth for all v2 API operations
auth_basic "Docker Registry";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://registry;
proxy_set_header Host $http_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;
proxy_read_timeout 900;
proxy_connect_timeout 60;
proxy_send_timeout 60;
}
# Default location - deny all
location / {
return 404;
}
# Logging
access_log /var/log/nginx/registry_auth_access.log;
error_log /var/log/nginx/registry_auth_error.log;
}
# Redirect HTTP to HTTPS (optional - for port 80 if needed)
server {
listen 80;
server_name _;
return 301 https://$server_name$request_uri;
}