Switch to Caddy + 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-07-13 10:26:07 -04:00
parent 0c7b65ad70
commit 03dac72b90
6 changed files with 292 additions and 288 deletions

View file

@ -53,13 +53,8 @@ jobs:
timeout 15 bash -c 'until docker exec ci-dind docker version > /dev/null 2>&1; do echo "Waiting for Docker daemon inside DinD..."; sleep 5; done'
echo "DinD container is ready"
# Copy Harbor certificate to DinD container
docker cp /etc/ssl/registry/registry.crt ci-dind:/usr/local/share/ca-certificates/
docker exec ci-dind chown root:root /usr/local/share/ca-certificates/registry.crt
docker exec ci-dind update-ca-certificates
# Login to Harbor registry (using HTTPS port 443)
echo "${{ secrets.HARBOR_CI_PASSWORD }}" | docker exec -i ci-dind docker login ${{ secrets.CI_HOST }}:443 -u ${{ secrets.HARBOR_CI_USER }} --password-stdin
# Login to Docker Registry (using HTTPS port 443)
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker exec -i ci-dind docker login ${{ secrets.CI_HOST }}:443 -u ${{ secrets.REGISTRY_USER }} --password-stdin
echo "DinD container setup complete"
fi
@ -78,46 +73,46 @@ jobs:
docker exec ci-dind rm -rf /workspace/* /workspace/.* 2>/dev/null || true
docker cp /tmp/ci-workspace/. ci-dind:/workspace/
- name: Check and prepare Harbor base images
- name: Check and prepare Docker Registry base images
run: |
# Set environment variables
export CI_HOST="${{ secrets.CI_HOST }}"
export APP_NAME="${{ secrets.APP_NAME || 'sharenet' }}"
export HARBOR_CI_USER="${{ secrets.HARBOR_CI_USER }}"
export HARBOR_CI_PASSWORD="${{ secrets.HARBOR_CI_PASSWORD }}"
export REGISTRY_USER="${{ secrets.REGISTRY_USER }}"
export REGISTRY_PASSWORD="${{ secrets.REGISTRY_PASSWORD }}"
# Login to Harbor
echo "Logging into Harbor registry..."
echo "$HARBOR_CI_PASSWORD" | docker exec -i ci-dind docker login "$CI_HOST:443" -u "$HARBOR_CI_USER" --password-stdin
# Login to Docker Registry
echo "Logging into Docker Registry..."
echo "$REGISTRY_PASSWORD" | docker exec -i ci-dind docker login "$CI_HOST:443" -u "$REGISTRY_USER" --password-stdin
# Check if base images exist in Harbor, pull from Docker Hub if not
# Check if base images exist in Docker Registry, pull from Docker Hub if not
BASE_IMAGES=("rust:1.75-slim" "node:20-slim" "postgres:15-alpine")
for image in "${BASE_IMAGES[@]}"; do
image_name=$(echo "$image" | cut -d: -f1)
image_tag=$(echo "$image" | cut -d: -f2)
harbor_image="$CI_HOST:443/$APP_NAME/$image_name:$image_tag"
registry_image="$CI_HOST:443/$APP_NAME/$image_name:$image_tag"
echo "Checking if $harbor_image exists in Harbor..."
echo "Checking if $registry_image exists in Docker Registry..."
# Try to pull from Harbor first
if docker exec ci-dind docker pull "$harbor_image" 2>/dev/null; then
echo "✓ Found $harbor_image in Harbor"
# Try to pull from Docker Registry first
if docker exec ci-dind docker pull "$registry_image" 2>/dev/null; then
echo "✓ Found $registry_image in Docker Registry"
else
echo "✗ $harbor_image not found in Harbor, pulling from Docker Hub..."
echo "✗ $registry_image not found in Docker Registry, pulling from Docker Hub..."
# Pull from Docker Hub
if docker exec ci-dind docker pull "$image"; then
echo "✓ Successfully pulled $image from Docker Hub"
# Tag for Harbor
docker exec ci-dind docker tag "$image" "$harbor_image"
# Tag for Docker Registry
docker exec ci-dind docker tag "$image" "$registry_image"
# Push to Harbor
if docker exec ci-dind docker push "$harbor_image"; then
echo "✓ Successfully pushed $harbor_image to Harbor"
# Push to Docker Registry
if docker exec ci-dind docker push "$registry_image"; then
echo "✓ Successfully pushed $registry_image to Docker Registry"
else
echo "✗ Failed to push $harbor_image to Harbor"
echo "✗ Failed to push $registry_image to Docker Registry"
exit 1
fi
else
@ -127,7 +122,7 @@ jobs:
fi
done
echo "All base images are ready in Harbor!"
echo "All base images are ready in Docker Registry!"
- name: Start testing environment
run: |
@ -332,11 +327,10 @@ jobs:
- name: Make scripts executable
run: chmod +x scripts/*.sh
- name: Configure Docker for Harbor access
- name: Configure Docker for Docker Registry access
run: |
# Configure Docker to access Harbor registry on CI Linode (using HTTPS)
# The Harbor certificate should already be installed on the production server
# as described in the CI guide Step 13
# Configure Docker to access Docker Registry on CI Linode (using HTTPS)
# Since we're using Caddy with automatic HTTPS, no certificate configuration is needed
# Wait for Docker to be ready
timeout 30 bash -c 'until docker info; do sleep 1; done'
@ -351,8 +345,8 @@ jobs:
- name: Pull and deploy application
run: |
# Pull latest images from Harbor registry
echo "Pulling latest images from Harbor registry..."
# Pull latest images from Docker Registry
echo "Pulling latest images from Docker Registry..."
docker compose -f docker-compose.prod.yml pull
# Deploy the application stack

View file

@ -8,7 +8,7 @@ This guide covers setting up a complete Continuous Integration/Continuous Deploy
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Forgejo Host │ │ CI/CD Linode │ │ Production Linode│
│ (Repository) │ │ (Actions Runner)│ │ (Docker Deploy) │
│ │ │ + Harbor Registry│ │ │
│ │ │ + Docker Registry│ │ │
│ │ │ + DinD Container│ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
@ -23,7 +23,7 @@ This guide covers setting up a complete Continuous Integration/Continuous Deploy
1. **Code Push**: Developer pushes code to Forgejo repository
2. **Automated Testing**: CI/CD Linode runs tests in isolated DinD environment
3. **Image Building**: If tests pass, Docker images are built within DinD
4. **Registry Push**: Images are pushed to Harbor registry from DinD
4. **Registry Push**: Images are pushed to Docker Registry from DinD
5. **Production Deployment**: Production Linode pulls images and deploys
6. **Health Check**: Application is verified and accessible
@ -36,9 +36,9 @@ This guide covers setting up a complete Continuous Integration/Continuous Deploy
- ✅ **Fast cleanup** - just restart DinD container
### **For CI/CD Operations:**
- ✅ **Zero resource contention** with Harbor
- ✅ **Zero resource contention** with Docker Registry
- ✅ **Simple cleanup** - one-line container restart
- ✅ **Perfect isolation** - CI/CD can't affect Harbor
- ✅ **Perfect isolation** - CI/CD can't affect Docker Registry
- ✅ **Consistent environment** - same setup every time
### **For Maintenance:**
@ -65,10 +65,9 @@ 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
- Harbor container registry for image storage
- Harbor web UI for image management
- Built-in vulnerability scanning with Trivy
- Role-based access control and audit logs
- Docker Registry with Caddy reverse proxy for image storage
- Unauthenticated pulls, authenticated pushes
- Automatic HTTPS with Caddy
- Secure SSH communication with production
- **Simplified cleanup** - just restart DinD container
@ -85,7 +84,7 @@ This guide covers setting up a complete Continuous Integration/Continuous Deploy
- **Automated deployment** to production
- **Rollback capability** with image versioning
- **Health monitoring** and logging
- **Zero resource contention** between CI/CD and Harbor
- **Zero resource contention** between CI/CD and Docker Registry
## Security Model and User Separation
@ -654,183 +653,133 @@ 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 Harbor Container Registry
### Step 5: Set Up Docker Registry with Caddy
Harbor provides a secure, enterprise-grade container registry with vulnerability scanning, role-based access control, and audit logging.
We'll set up a basic Docker Registry with Caddy as a reverse proxy, configured to allow unauthenticated pulls but require authentication for pushes.
#### 5.1 Create Harbor Service User
#### 5.1 Create Registry Service User
```bash
# Create dedicated user and group for Harbor
sudo groupadd -r harbor
sudo useradd -r -g harbor -s /bin/bash -m -d /opt/harbor harbor
# Create dedicated user and group for Docker Registry
sudo groupadd -r registry
sudo useradd -r -g registry -s /bin/bash -m -d /opt/registry registry
# Set secure password for emergency access
echo "harbor:$(openssl rand -base64 32)" | sudo chpasswd
echo "registry:$(openssl rand -base64 32)" | sudo chpasswd
# Add harbor user to docker group
sudo usermod -aG docker harbor
# Add registry user to docker group
sudo usermod -aG docker registry
# Add CI_DEPLOY_USER to harbor group for monitoring access
sudo usermod -aG harbor CI_DEPLOY_USER
# Add CI_DEPLOY_USER to registry group for monitoring access
sudo usermod -aG registry CI_DEPLOY_USER
# Set proper permissions on /opt/harbor directory
sudo chown harbor:harbor /opt/harbor
sudo chmod 755 /opt/harbor
# Set proper permissions on /opt/registry directory
sudo chown registry:registry /opt/registry
sudo chmod 755 /opt/registry
```
#### 5.2 Generate SSL Certificates
#### 5.2 Create Docker Compose Setup
```bash
# Create system SSL directory for Harbor certificates
sudo mkdir -p /etc/ssl/registry
# Create registry directory structure
sudo mkdir -p /opt/registry
sudo chown registry:registry /opt/registry
cd /opt/registry
# Get your actual IP address
YOUR_ACTUAL_IP=$(curl -4 -s ifconfig.me)
echo "Your IP address is: $YOUR_ACTUAL_IP"
# Copy registry configuration from repository
# The registry folder contains the Docker Compose and Caddy configuration files
sudo cp /opt/APP_NAME/registry/docker-compose.registry.yml docker-compose.yml
sudo cp /opt/APP_NAME/registry/Caddyfile Caddyfile
# Create OpenSSL configuration file with proper SANs
sudo tee /etc/ssl/registry/openssl.conf << EOF
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no
# Update Caddyfile with your actual IP address
sudo sed -i "s/registry.example.com/YOUR_CI_CD_IP/g" Caddyfile
[req_distinguished_name]
C = US
ST = State
L = City
O = Organization
CN = $YOUR_ACTUAL_IP
# Create environment file for registry authentication
# First, create a secure password hash
REGISTRY_PASSWORD="your-secure-registry-password"
REGISTRY_PASSWORD_HASH=$(htpasswd -nbB registry-user "$REGISTRY_PASSWORD" | cut -d: -f2)
[v3_req]
keyUsage = keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
IP.1 = $YOUR_ACTUAL_IP
DNS.1 = $YOUR_ACTUAL_IP
DNS.2 = localhost
sudo tee .env << EOF
REGISTRY_USERNAME=registry-user
REGISTRY_PASSWORD_HASH=$REGISTRY_PASSWORD_HASH
EOF
# Generate self-signed certificate with proper SANs
sudo openssl req -x509 -newkey rsa:4096 -keyout /etc/ssl/registry/registry.key -out /etc/ssl/registry/registry.crt -days 365 -nodes -extensions v3_req -config /etc/ssl/registry/openssl.conf
# Set proper permissions for harbor user
sudo chown harbor:harbor /etc/ssl/registry/registry.key
sudo chown harbor:harbor /etc/ssl/registry/registry.crt
sudo chmod 600 /etc/ssl/registry/registry.key
sudo chmod 644 /etc/ssl/registry/registry.crt
sudo chmod 644 /etc/ssl/registry/openssl.conf
# Set proper permissions
sudo chown registry:registry .env
sudo chmod 600 .env
```
#### 5.3 Configure Docker to Trust Harbor Registry
#### 5.3 Configure Docker Registry
```bash
# Add the certificate to system CA certificates
sudo cp /etc/ssl/registry/registry.crt /usr/local/share/ca-certificates/registry.crt
sudo update-ca-certificates
# Create registry data directory
sudo mkdir -p /opt/registry/data
sudo chown registry:registry /opt/registry/data
# Restart Docker to ensure it picks up the new CA certificates
sudo systemctl restart docker
# Create registry configuration (no authentication needed - Caddy handles it)
sudo tee /opt/registry/config.yml << 'EOF'
version: 0.1
log:
level: debug
storage:
filesystem:
rootdirectory: /var/lib/registry
delete:
enabled: true
http:
addr: :5000
headers:
X-Content-Type-Options: [nosniff]
middleware:
repository:
- name: AwsEc2PublicBlock
storage:
- name: Redirect
options:
baseurl: https://YOUR_CI_CD_IP
EOF
# Set proper permissions
sudo chown registry:registry /opt/registry/config.yml
```
#### 5.4 Install Harbor
#### 5.5 Start Docker Registry with Docker Compose
```bash
# Switch to harbor user
sudo su - harbor
# Switch to registry user
sudo su - registry
# Set DB_PASSWORD environment variable
export DB_PASSWORD=$(openssl rand -base64 32 | tr -d "=+/" | cut -c1-25)
# Navigate to registry directory
cd /opt/registry
# IMPORTANT: Save the DB_PASSWORD in your password manager for safekeeping
echo "DB_PASSWORD: $DB_PASSWORD"
# Download and install Harbor
cd /opt/harbor
# Switch to the CI_DEPLOY_USER
sudo su - CI_DEPLOY_USER
sudo wget https://github.com/goharbor/harbor/releases/download/v2.10.0/harbor-offline-installer-v2.10.0.tgz
sudo tar -xzf harbor-offline-installer-v2.10.0.tgz
cd harbor
sudo cp harbor.yml.tmpl harbor.yml
# Edit harbor.yml configuration
sudo nano harbor.yml
```
**Important**: In the `harbor.yml` file, update:
- `hostname: YOUR_CI_CD_IP` (replace with your actual IP)
- `certificate: /etc/ssl/registry/registry.crt`
- `private_key: /etc/ssl/registry/registry.key`
- `password: <the DB_PASSWORD generated above>`
**Note**: The default Harbor admin password is "Harbor12345" and will be changed in Step 5.6
```bash
# Run the following as the CI_DEPLOY_USER
sudo su - CI_DEPLOY_USER
cd /opt/harbor/harbor
# Install Harbor with Trivy vulnerability scanner
sudo ./prepare
sudo ./install.sh --with-trivy
sudo docker compose down
sudo chown -R harbor:harbor harbor
# Switch to the harbor user
sudo su - harbor
cd /opt/harbor/harbor
# Run the following to patially adjust the permissions correctly for the harbor user
./install.sh --with-trivy
# Exit harbor user shell to switch back to the CI_DEPLOY_USER
exit
cd /opt/harbor/harbor
# Run the following to adjust the permissions for various en files
sudo chown harbor:harbor common/config/jobservice/env
sudo chown harbor:harbor common/config/db/env
sudo chown harbor:harbor common/config/registryctl/env
sudo chown harbor:harbor common/config/trivy-adapter/env
sudo chown harbor:harbor common/config/core/env
# Switch back to harbor user and bring Harbor back up
sudo su - harbor
cd /opt/harbor/harbor
# Start the Docker Registry and Caddy services
docker compose up -d
# Verify that all Harbor containers are healthy
docker compose ps -a
# Verify services are running
docker compose ps
# Verify using the Harbor API that all Harbor processes are healthy
curl -k https://localhost/api/v2.0/health
# Exit registry user shell
exit
```
#### 5.5 Create Systemd Service
#### 5.6 Create Systemd Service for Docker Compose
```bash
# Create systemd service file for Harbor
sudo tee /etc/systemd/system/harbor.service << EOF
# Create systemd service file for Docker Registry with Docker Compose
sudo tee /etc/systemd/system/docker-registry.service << EOF
[Unit]
Description=Harbor Container Registry
Description=Docker Registry with Caddy
After=docker.service
Requires=docker.service
[Service]
Type=oneshot
RemainAfterExit=yes
User=harbor
Group=harbor
WorkingDirectory=/opt/harbor/harbor
User=registry
Group=registry
WorkingDirectory=/opt/registry
ExecStart=/usr/bin/docker compose up -d
ExecStop=/usr/bin/docker compose down
ExecReload=/usr/bin/docker compose down && /usr/bin/docker compose up -d
@ -839,32 +788,39 @@ ExecReload=/usr/bin/docker compose down && /usr/bin/docker compose up -d
WantedBy=multi-user.target
EOF
# Enable and start Harbor service
# Enable and start Docker Registry service
sudo systemctl daemon-reload
sudo systemctl enable harbor.service
sudo systemctl start harbor.service
sudo systemctl enable docker-registry.service
sudo systemctl start docker-registry.service
# Monitor startup (can take 2-3 minutes)
sudo journalctl -u harbor.service -f
# Monitor startup
sudo journalctl -u docker-registry.service -f
```
#### 5.6 Configure Harbor Access
#### 5.7 Configure Registry Access
1. **Access Harbor Web UI**: Open `https://YOUR_CI_CD_IP` in your browser
2. **Login**: Username `admin`, Password `Harbor12345`
3. **Change admin password**: Click admin icon → Change Password
4. **Create project**: Projects → New Project → Name: `APP_NAME`, Access Level: `Public`
5. **Create CI user**: Administration → Users → New User → Username: `ci-user`, Password: `your-secure-password`
6. **Assign role**: Projects → `APP_NAME` → Members → + User → Select `ci-user`, Role: `Developer`
The Docker Registry is now configured with the following access model:
#### 5.7 Test Harbor Setup
**Authentication Model:**
- **Pulls**: Unauthenticated (public read access)
- **Pushes**: Require authentication with `registry-user` credentials
**Registry Credentials:**
- **Username**: `registry-user`
- **Password**: The password you set in the environment file (default: `your-secure-registry-password`)
**Registry URL**: `https://YOUR_CI_CD_IP`
**Note**: The authentication is handled by Caddy using the environment variables in the `.env` file. The Docker Registry itself runs without authentication, but Caddy enforces authentication for push operations.
#### 5.8 Test Registry Setup
```bash
# Switch to CI_SERVICE_USER for testing (CI_SERVICE_USER runs CI pipeline and Docker operations)
sudo su - CI_SERVICE_USER
# Test Docker login and push
echo "your-secure-password" | docker login YOUR_CI_CD_IP -u ci-user --password-stdin
echo "your-secure-registry-password" | docker login YOUR_CI_CD_IP -u registry-user --password-stdin
# Create and push test image
echo "FROM alpine:latest" > /tmp/test.Dockerfile
@ -888,10 +844,10 @@ exit
```
**Expected behavior**:
- ✅ Push requires authentication
- ✅ Pull works without authentication
- ✅ Push requires authentication with `registry-user` credentials
- ✅ Pull works without authentication (public read access)
- ✅ Unauthorized push is blocked
- ✅ Web UI accessible at `https://YOUR_CI_CD_IP`
- ✅ Registry accessible at `https://YOUR_CI_CD_IP`
### Step 6: Install Forgejo Actions Runner
@ -1160,31 +1116,16 @@ docker exec ci-dind docker version
**Why CI_SERVICE_USER**: The CI_SERVICE_USER is in the docker group and runs the CI pipeline, so it needs direct access to the DinD container for seamless CI/CD operations.
#### 7.2 Configure DinD for Harbor Registry
#### 7.2 Configure DinD for Docker Registry
```bash
# Navigate to the application directory
cd /opt/APP_NAME
# Copy Harbor certificate to DinD container
docker cp /etc/ssl/registry/registry.crt ci-dind:/usr/local/share/ca-certificates/
# Login to Docker Registry from within DinD
echo "your-registry-password" | docker exec -i ci-dind docker login YOUR_CI_CD_IP -u registry-user --password-stdin
# Fix certificate ownership (crucial for CA certificate trust)
docker exec ci-dind chown root:root /usr/local/share/ca-certificates/registry.crt
# Update CA certificates
docker exec ci-dind update-ca-certificates
# Restart DinD container to pick up new CA certificates
docker restart ci-dind
# Wait for DinD to be ready again
sleep 30
# Login to Harbor from within DinD
echo "ci-user-password" | docker exec -i ci-dind docker login YOUR_CI_CD_IP -u ci-user --password-stdin
# Test Harbor connectivity from DinD (using certificate trust)
# Test Docker Registry connectivity from DinD
docker exec ci-dind docker pull alpine:latest
docker exec ci-dind docker tag alpine:latest YOUR_CI_CD_IP/APP_NAME/test:latest
docker exec ci-dind docker push YOUR_CI_CD_IP/APP_NAME/test:latest
@ -1242,32 +1183,32 @@ The CI/CD pipeline uses a three-stage approach with dedicated environments for e
- Rust toolchain for backend testing and migrations
- Node.js toolchain for frontend testing
- **Network**: All containers communicate through `ci-cd-test-network`
- **Setup**: DinD container created, Harbor certificate installed, Docker login performed, code cloned into DinD from Forgejo
- **Setup**: DinD container created, Docker Registry login performed, code cloned into DinD from Forgejo
- **Cleanup**: Testing containers removed, DinD container kept running
**Job 2 (Building) - Direct Docker Commands:**
- **Purpose**: Image building and pushing to Harbor
- **Purpose**: Image building and pushing to Docker Registry
- **Environment**: Same DinD container from Job 1
- **Code Access**: Reuses code from Job 1, updates to latest commit
- **Process**:
- Uses Docker Buildx for efficient building
- Builds backend and frontend images separately
- Pushes images to Harbor registry
- **Harbor Access**: Reuses Harbor authentication from Job 1
- Pushes images to Docker Registry
- **Registry Access**: Reuses Docker Registry authentication from Job 1
- **Cleanup**: DinD container stopped and removed (clean slate for next run)
**Job 3 (Deployment) - `docker-compose.prod.yml`:**
- **Purpose**: Production deployment with pre-built images
- **Environment**: Production runner on Production Linode
- **Process**:
- Pulls images from Harbor registry
- Pulls images from Docker Registry
- Deploys complete application stack
- Verifies all services are healthy
- **Services**: PostgreSQL, backend, frontend, Nginx
**Key Benefits:**
- **🧹 Complete Isolation**: Each job has its own dedicated environment
- **🚫 No Resource Contention**: Testing and building don't interfere with Harbor
- **🚫 No Resource Contention**: Testing and building don't interfere with Docker Registry
- **⚡ Consistent Environment**: Same setup every time
- **🎯 Purpose-Specific**: Each Docker Compose file serves a specific purpose
- **🔄 Parallel Safety**: Jobs can run safely in parallel
@ -1278,7 +1219,7 @@ The CI/CD pipeline uses a three-stage approach with dedicated environments for e
# Test DinD functionality
docker exec ci-dind docker run --rm alpine:latest echo "DinD is working!"
# Test Harbor integration
# Test Docker Registry integration
docker exec ci-dind docker pull alpine:latest
docker exec ci-dind docker tag alpine:latest YOUR_CI_CD_IP/APP_NAME/dind-test:latest
docker exec ci-dind docker push YOUR_CI_CD_IP/APP_NAME/dind-test:latest
@ -1290,11 +1231,11 @@ docker exec ci-dind docker rmi YOUR_CI_CD_IP/APP_NAME/dind-test:latest
**Expected Output**:
- DinD container should be running and accessible
- Docker commands should work inside DinD
- Harbor push/pull should work from DinD
- Docker Registry push/pull should work from DinD
#### 7.4 Production Deployment Architecture
The production deployment uses a separate Docker Compose file (`docker-compose.prod.yml`) that pulls built images from the Harbor registry and deploys the complete application stack.
The production deployment uses a separate Docker Compose file (`docker-compose.prod.yml`) that pulls built images from the Docker Registry and deploys the complete application stack.
**Production Stack Components:**
- **PostgreSQL**: Production database with persistent storage
@ -1304,12 +1245,12 @@ The production deployment uses a separate Docker Compose file (`docker-compose.p
**Deployment Flow:**
1. **Production Runner**: Runs on Production Linode with `production` label
2. **Image Pull**: Pulls latest images from Harbor registry on CI Linode
2. **Image Pull**: Pulls latest images from Docker Registry on CI Linode
3. **Stack Deployment**: Uses `docker-compose.prod.yml` to deploy complete stack
4. **Health Verification**: Ensures all services are healthy before completion
**Key Benefits:**
- **🔄 Image Registry**: Centralized image storage in Harbor
- **🔄 Image Registry**: Centralized image storage in Docker Registry
- **📦 Consistent Deployment**: Same images tested in CI are deployed to production
- **⚡ Fast Deployment**: Only pulls changed images
- **🛡️ Rollback Capability**: Can easily rollback to previous image versions
@ -1348,11 +1289,11 @@ sudo ufw --force enable
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 443/tcp # Harbor registry (public read access)
sudo ufw allow 443/tcp # Docker Registry via Caddy (public read access)
```
**Security Model**:
- **Port 443 (Harbor)**: Public read access for public projects, authenticated write access
- **Port 443 (Docker Registry)**: Public read access, authenticated write access
- **SSH**: Restricted to your IP addresses
- **All other ports**: Blocked
@ -1660,43 +1601,18 @@ ls -la /opt/APP_NAME
- Sets proper ownership for the PROD_SERVICE_USER
- Ensures the directory exists before the CI workflow runs
### Step 13: Configure Docker for Harbor Access
### Step 13: Configure Docker for Docker Registry Access
**Important**: The Production Linode needs to be able to pull Docker images from the Harbor registry on the CI/CD Linode. We need to configure Docker to trust the Harbor SSL certificate.
**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.
```bash
# Add Harbor certificate to system CA certificates
sudo mkdir -p /usr/local/share/ca-certificates
# Copy Harbor certificate from CI Linode to local machine, then to Production Linode
# First, from your local machine, copy the certificate from CI Linode:
scp CI_DEPLOY_USER@YOUR_CI_CD_IP:/etc/ssl/registry/registry.crt ./
# Then copy it to the Production Linode:
scp registry.crt PROD_DEPLOY_USER@YOUR_PRODUCTION_IP:/tmp/
# Remove the cert from your local machine as no longer needed
rm registry.crt
# Now on the Production Linode, move it to the correct location:
sudo mv /tmp/registry.crt /usr/local/share/ca-certificates/
# Fix certificate ownership (crucial for CA certificate trust)
sudo chown root:root /usr/local/share/ca-certificates/registry.crt
# Update CA certificates
sudo update-ca-certificates
# Restart Docker to apply changes
sudo systemctl restart docker
# Change to the PROD_SERVICE_USER
sudo su - PROD_SERVICE_USER
# Test that the certificate is working by pulling an image from Harbor
# Test that Docker can pull images from the Docker Registry
docker pull YOUR_CI_CD_IP/APP_NAME/test:latest
# If the pull succeeds, the certificate is working correctly
# If the pull succeeds, the Docker Registry is accessible
# Change back to PROD_DEPLOY_USER
exit
@ -1705,21 +1621,13 @@ exit
**Important**: Replace `YOUR_CI_CD_IP` with your actual CI/CD Linode IP address.
**What this does**:
- **Copies Harbor certificate**: Transfers the SSL certificate from CI Linode to Production Linode via your local machine
- **Configures certificate trust**: Properly sets up Harbor certificate trust in Docker
- **Fixes ownership issues**: Ensures certificate has correct ownership for CA trust
- **Updates CA certificates**: Makes the certificate available to all applications
- **Restarts Docker**: Applies the new configuration
- **Tests certificate**: Verifies that Docker can successfully pull images from Harbor
**Note**: Since you don't have direct SSH access between the Linodes, you'll need to copy the certificate through your local machine using the deployment users:
1. From your local machine: `scp CI_DEPLOY_USER@YOUR_CI_CD_IP:/etc/ssl/registry/registry.crt ./`
2. Then: `scp registry.crt PROD_DEPLOY_USER@YOUR_PRODUCTION_IP:/tmp/`
3. On Production Linode: `sudo mv /tmp/registry.crt /usr/local/share/ca-certificates/`
- **Tests Docker Registry access**: Verifies that Docker can successfully pull images from the Docker Registry
- **No certificate configuration needed**: Caddy handles HTTPS automatically
- **Simple setup**: No complex certificate management required
### Step 14: Set Up Forgejo Runner for Production Deployment
**Important**: The Production Linode needs a Forgejo runner to execute the deployment job from the CI/CD workflow. This runner will pull images from Harbor and deploy using `docker-compose.prod.yml`.
**Important**: The Production Linode needs a Forgejo runner to execute the deployment job from the CI/CD workflow. This runner will pull images from Docker Registry and deploy using `docker-compose.prod.yml`.
#### 14.1 Download Runner
@ -1870,7 +1778,7 @@ sudo journalctl -u forgejo-runner.service -f --no-pager
When the workflow runs, it will:
1. Pull the latest Docker images from Harbor registry
1. Pull the latest Docker images from Docker Registry
2. Use the `docker-compose.prod.yml` file to deploy the application stack
3. Create the necessary environment variables for production deployment
4. Verify that all services are healthy after deployment
@ -1882,7 +1790,7 @@ The production runner will automatically handle the deployment process when you
The `docker-compose.prod.yml` file is specifically designed for production deployment and differs from development setups:
**Key Features**:
- **Image-based deployment**: Uses pre-built images from Harbor registry instead of building from source
- **Image-based deployment**: Uses pre-built images from Docker Registry instead of building from source
- **Production networking**: All services communicate through a dedicated `sharenet-network`
- **Health checks**: Each service includes health checks to ensure proper startup order
- **Nginx reverse proxy**: Includes Nginx for SSL termination, load balancing, and security headers
@ -1896,7 +1804,7 @@ The `docker-compose.prod.yml` file is specifically designed for production deplo
4. **Nginx**: Reverse proxy that serves the frontend and proxies API requests to backend
**Deployment Process**:
1. The production runner pulls the latest images from Harbor registry
1. The production runner pulls the latest images from Docker Registry
2. Creates environment variables for the deployment
3. Runs `docker compose -f docker-compose.prod.yml up -d`
4. Waits for all services to be healthy
@ -2010,7 +1918,7 @@ docker compose --version
#### 16.2 Test Harbor Access
```bash
# Test pulling an image from the CI/CD Harbor registry
# Test pulling an image from the CI/CD Docker Registry
docker pull YOUR_CI_CD_IP/APP_NAME/test:latest
```
@ -2027,20 +1935,20 @@ docker pull YOUR_CI_CD_IP/APP_NAME/test:latest
Go to your Forgejo repository and add these secrets in **Settings → Secrets and Variables → Actions**:
**Required Secrets:**
- `CI_HOST`: Your CI/CD Linode IP address (used for Harbor registry access)
- `CI_HOST`: Your CI/CD Linode IP address (used for Docker Registry access)
- `PRODUCTION_IP`: Your Production Linode IP address
- `PROD_DEPLOY_USER`: The production deployment user name (e.g., `prod-deploy`)
- `PROD_SERVICE_USER`: The production service user name (e.g., `prod-service`)
- `APP_NAME`: Your application name (e.g., `sharenet`)
- `POSTGRES_PASSWORD`: A strong password for the PostgreSQL database
- `HARBOR_CI_USER`: Harbor username for CI operations (e.g., `ci-user`)
- `HARBOR_CI_PASSWORD`: Harbor password for CI operations (the password you set for ci-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 environment file, default: `your-secure-registry-password`)
**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 Harbor registry. The CI pipeline automatically checks if base images exist in Harbor and pulls them from Docker Hub only when needed, eliminating rate limiting issues and providing better control over the testing environment.
**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.
### Step 18: Test Complete Pipeline
@ -2060,24 +1968,24 @@ The pipeline should execute these steps in order:
4. **Test Frontend**: Run frontend tests in isolated environment
5. **Build Backend**: Build backend Docker image in DinD
6. **Build Frontend**: Build frontend Docker image in DinD
7. **Push to Registry**: Push images to Harbor registry from DinD
7. **Push to Registry**: Push images to Docker Registry from DinD
8. **Deploy to Production**: Deploy to production server
#### 18.3 Check Harbor
#### 18.3 Check Docker Registry
```bash
# On CI/CD Linode
cd /opt/APP_NAME
# Check if new images were pushed (using correct Harbor port 443)
# Check if new images were pushed (using correct registry port 443)
curl -k https://localhost:443/v2/_catalog
# Check specific repository tags (using correct Harbor API structure)
# Check specific repository tags
curl -k https://localhost:443/v2/APP_NAME/backend/tags/list
curl -k https://localhost:443/v2/APP_NAME/frontend/tags/list
# Alternative: Check Harbor web UI
# Open https://YOUR_CI_CD_IP in your browser and navigate to Projects → APP_NAME
# Alternative: Check registry via Caddy
# Open https://YOUR_CI_CD_IP in your browser
```
#### 18.4 Verify Production Deployment
@ -2213,7 +2121,7 @@ tail -f /tmp/monitor.log
You have successfully set up a complete CI/CD pipeline with:
- ✅ **Automated testing** on every code push in isolated DinD environment
- ✅ **Docker image building** and Harbor registry storage
- ✅ **Docker image building** and Docker Registry storage
- ✅ **Automated deployment** to production
- ✅ **Health monitoring** and logging
- ✅ **Backup and cleanup** automation

25
registry/Caddyfile Normal file
View file

@ -0,0 +1,25 @@
(registry_auth) {
basicauth {
{env.REGISTRY_USERNAME} {env.REGISTRY_PASSWORD_HASH}
}
}
https://registry.example.com {
import registry_auth
reverse_proxy registry:5000
header {
X-Content-Type-Options nosniff
}
@push method POST PUT PATCH DELETE
handle @push {
import registry_auth
reverse_proxy registry:5000
}
@pull method GET HEAD OPTIONS
handle @pull {
reverse_proxy registry:5000
}
}

39
registry/README.md Normal file
View file

@ -0,0 +1,39 @@
# Docker Registry Configuration
This folder contains the configuration files for the Docker Registry setup used in the CI/CD pipeline.
## Files
- **`docker-compose.registry.yml`**: Docker Compose configuration for the registry and Caddy reverse proxy
- **`Caddyfile`**: Caddy configuration for HTTPS and authentication
- **`README.md`**: This documentation file
## Architecture
The registry setup uses:
- **Docker Registry**: Basic registry for storing Docker images
- **Caddy**: Reverse proxy with automatic HTTPS and authentication
- **Environment Variables**: For authentication credentials
## Authentication Model
- **Pulls**: Unauthenticated (public read access)
- **Pushes**: Require authentication with `registry-user` credentials
## Configuration
The setup is configured through:
1. **Environment Variables**: Stored in `.env` file (created during setup)
2. **Caddyfile**: Handles HTTPS and authentication
3. **Docker Compose**: Orchestrates the registry and Caddy services
## Usage
The registry is automatically set up during the CI/CD pipeline setup process. The configuration files are copied from this folder to the registry server and customized with the appropriate IP address and credentials.
## Security
- Authentication is handled by Caddy using environment variables
- HTTPS is automatically managed by Caddy
- Registry data is persisted in Docker volumes
- Environment file contains sensitive credentials and should be properly secured

View file

@ -0,0 +1,38 @@
version: '3'
services:
registry:
image: registry:2
container_name: registry
networks:
- sharenet-ci
volumes:
- registry-data:/var/lib/registry
ports:
- "127.0.0.1:5000:5000" # Localhost only
caddy:
image: caddy:latest
container_name: caddy
depends_on:
- registry
networks:
- sharenet-ci
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
env_file:
- .env
volumes:
registry-data:
caddy_data:
caddy_config:
networks:
sharenet-ci:
driver: bridge

View file

@ -111,12 +111,12 @@ monitor_ci_cd() {
echo "Uptime: $(uptime)"
echo
log_info "Harbor Status:"
if docker ps --format "table {{.Names}}\t{{.Status}}" | grep -q harbor; then
log_success "Harbor containers are running"
docker ps --format "table {{.Names}}\t{{.Status}}" | grep harbor
log_info "Docker Registry Status:"
if docker ps --format "table {{.Names}}\t{{.Status}}" | grep -q registry; then
log_success "Docker Registry containers are running"
docker ps --format "table {{.Names}}\t{{.Status}}" | grep registry
else
log_error "Harbor containers are not running"
log_error "Docker Registry containers are not running"
fi
echo
@ -144,10 +144,10 @@ monitor_ci_cd() {
# Registry health check
log_info "Registry Health Check:"
if curl -s -f -k https://localhost/api/v2.0/health > /dev/null 2>&1; then
log_success "Harbor registry is accessible"
if curl -s -f -k https://localhost/v2/_catalog > /dev/null 2>&1; then
log_success "Docker Registry is accessible"
else
log_error "Harbor registry is not accessible"
log_error "Docker Registry is not accessible"
fi
}