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
- Forgejo Actions runner for automated builds
- **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
- Unauthenticated pulls, authenticated pushes
- Automatic HTTPS with Caddy
- Automatic HTTPS with nginx
- Secure SSH communication with production
- **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
```
### 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
@ -679,38 +679,34 @@ sudo chmod 755 /var/log/registry
# Navigate to the cloned application directory
cd /opt/APP_NAME/registry
# Update Caddyfile with your actual IP address
sudo sed -i "s/YOUR_CI_CD_IP/YOUR_ACTUAL_IP_ADDRESS/g" /opt/APP_NAME/registry/Caddyfile
# 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/nginx.conf
# 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_REGISTRY_NAME/APP_NAME-Registry/g" /opt/APP_NAME/registry/openssl.conf
# Create FHS-compliant environment directory
sudo mkdir -p /etc/registry/env
sudo chown CI_SERVICE_USER:CI_SERVICE_USER /etc/registry/env
sudo chmod 755 /etc/registry/env
# Create FHS-compliant authentication directory
sudo mkdir -p /etc/registry/auth
sudo chown CI_SERVICE_USER:CI_SERVICE_USER /etc/registry/auth
sudo chmod 755 /etc/registry/auth
# Create secure environment file for registry authentication
# First, create a secure password hash
# Create htpasswd file for nginx authentication
# Save this password somewhere safe
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
sudo tee /etc/registry/env/.env > /dev/null <<EOF
REGISTRY_PASSWORD_HASH=$REGISTRY_PASSWORD_HASH
EOF
# Create htpasswd file in FHS-compliant location
sudo htpasswd -cb /etc/registry/auth/.htpasswd registry-user "$REGISTRY_PASSWORD"
# Set secure permissions on .env file (owner read/write only)
sudo chown CI_SERVICE_USER:CI_SERVICE_USER /etc/registry/env/.env
sudo chmod 600 /etc/registry/env/.env
# Set secure permissions on htpasswd file (owner read/write only)
sudo chown CI_SERVICE_USER:CI_SERVICE_USER /etc/registry/auth/.htpasswd
sudo chmod 600 /etc/registry/auth/.htpasswd
# 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/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/docker-compose.registry.yml
```
@ -928,8 +924,8 @@ sudo systemctl status docker-registry.service
# Check that containers are running
sudo su - CI_SERVICE_USER -c "cd /opt/APP_NAME/registry && docker compose -f docker-compose.registry.yml ps"
# Check Caddy logs
sudo su - CI_SERVICE_USER -c "cd /opt/APP_NAME/registry && docker compose -f docker-compose.registry.yml logs caddy"
# Check nginx logs
sudo su - CI_SERVICE_USER -c "cd /opt/APP_NAME/registry && docker compose -f docker-compose.registry.yml logs nginx"
# Check Registry logs
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
openssl s_client -connect YOUR_ACTUAL_IP_ADDRESS:4443 -servername YOUR_ACTUAL_IP_ADDRESS < /dev/null
# Verify Caddy is using the correct certificates
docker exec caddy ls -la /etc/certs/
# Verify nginx is using the correct certificates
docker exec nginx ls -la /etc/registry/certs/
# If issues persist, restart Docker daemon to reload certificates
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/`):
- `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
- `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/requests/` - Certificate requests and configs (mode 644)
- `/etc/registry/certs/registry.crt` - Server certificate (mode 644)
- `/etc/registry/env/` - Environment variables and secrets:
- `/etc/registry/env/.env` - Registry authentication secrets (mode 600)
- `/etc/registry/auth/.htpasswd` - nginx authentication file (mode 600)
- `/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**:
- **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 allow outgoing
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**:
@ -1852,7 +1847,7 @@ ls -la /opt/APP_NAME
### 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
# Change to the PROD_SERVICE_USER
@ -1873,7 +1868,7 @@ exit
**What this does**:
- **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
### 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`)
- `POSTGRES_PASSWORD`: A strong password for the PostgreSQL database
- `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_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:
# - /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
# - /etc/registry/env/.env (contains the actual secrets)
# - /etc/registry/auth/.htpasswd (contains the actual secrets)
# - /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
- **Never committed to version control**
- **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

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
- **`docker-compose.registry.yml`**: Docker Compose configuration for the registry and Caddy reverse proxy
- **`Caddyfile`**: Caddy configuration for HTTPS and authentication
- **`docker-compose.registry.yml`**: Docker Compose configuration for the registry and nginx reverse proxy
- **`nginx.conf`**: nginx configuration for HTTPS and authentication
- **`docker-registry.service`**: Systemd service file for Docker Registry
- **`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:
- **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
- **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
@ -40,8 +40,8 @@ The registry setup uses:
The setup is configured through:
1. **Environment Variables**: Stored in `.env` file (created during setup) for authentication
2. **Docker Compose Environment**: Registry configuration via environment variables
3. **Caddyfile**: Handles HTTPS and authentication
4. **Docker Compose**: Orchestrates the registry and Caddy services
3. **nginx.conf**: Handles HTTPS and authentication
4. **Docker Compose**: Orchestrates the registry and nginx services
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
@ -51,8 +51,8 @@ The registry is automatically set up during the CI/CD pipeline setup process. Th
## Security
- Authentication is handled by Caddy using environment variables
- HTTPS is automatically managed by Caddy
- Authentication is handled by nginx using htpasswd file
- HTTPS is automatically managed by nginx
- Registry data is persisted in Docker volumes
- 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)

View file

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