Clean up steps for configuring DinD container to work with Harbor in CI workflow

This commit is contained in:
continuist 2025-06-30 21:40:32 -04:00
parent 4a4eddbb72
commit 6e07ea8d0f
2 changed files with 207 additions and 350 deletions

View file

@ -7,7 +7,7 @@ on:
branches: [ main ] branches: [ main ]
env: env:
REGISTRY: ${{ secrets.CI_HOST }}:5000 REGISTRY: ${{ secrets.CI_HOST }}:80
IMAGE_NAME: ${{ secrets.APP_NAME || 'sharenet' }} IMAGE_NAME: ${{ secrets.APP_NAME || 'sharenet' }}
jobs: jobs:
@ -21,21 +21,52 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup DinD Environment
run: |
# Check if DinD container already exists and is running
if ! docker ps --format "table {{.Names}}" | grep -q "^ci-dind$"; then
echo "Starting new DinD container..."
# Start DinD container for isolated CI operations
docker run -d \
--name ci-dind \
--privileged \
-p 2375:2375 \
-e DOCKER_TLS_CERTDIR="" \
docker:dind
# Wait for DinD to be ready
echo "Waiting for DinD container to be ready..."
timeout 60 bash -c 'until docker exec ci-dind docker version; do sleep 2; done'
# Copy Harbor certificate to DinD container
docker cp /etc/ssl/registry/registry.crt ci-dind:/usr/local/share/ca-certificates/
docker exec ci-dind update-ca-certificates
# Login to Harbor registry
echo "${{ secrets.HARBOR_CI_PASSWORD }}" | docker exec -i ci-dind docker login ${{ secrets.CI_HOST }}:80 -u ${{ secrets.HARBOR_CI_USER }} --password-stdin
echo "DinD container setup complete"
else
echo "DinD container already running, reusing existing setup"
# Verify DinD is still working
docker exec ci-dind docker version
fi
- name: Setup Containerized Testing Environment - name: Setup Containerized Testing Environment
run: | run: |
# Start testing environment using dedicated compose file # Start testing environment using dedicated compose file inside DinD
docker compose -f docker-compose.test.yml up -d docker exec ci-dind docker compose -f /workspace/docker-compose.test.yml up -d
# Wait for all services to be ready # Wait for all services to be ready
echo "Waiting for testing environment to be ready..." echo "Waiting for testing environment to be ready..."
timeout 120 bash -c 'until docker compose -f docker-compose.test.yml ps | grep -q "healthy" && docker compose -f docker-compose.test.yml ps | grep -q "Up"; do sleep 2; done' timeout 120 bash -c 'until docker exec ci-dind docker compose -f /workspace/docker-compose.test.yml ps | grep -q "healthy" && docker exec ci-dind docker compose -f /workspace/docker-compose.test.yml ps | grep -q "Up"; do sleep 2; done'
# Verify all containers are running # Verify all containers are running
docker compose -f docker-compose.test.yml ps docker exec ci-dind docker compose -f /workspace/docker-compose.test.yml ps
- name: Install SQLx CLI in Rust container - name: Install SQLx CLI in Rust container
run: | run: |
docker exec ci-cd-test-rust cargo install sqlx-cli --no-default-features --features postgres docker exec ci-dind docker exec ci-cd-test-rust cargo install sqlx-cli --no-default-features --features postgres
- name: Validate migration files - name: Validate migration files
env: env:
@ -43,16 +74,16 @@ jobs:
run: | run: |
# Wait for PostgreSQL to be ready # Wait for PostgreSQL to be ready
echo "Waiting for PostgreSQL to be ready..." echo "Waiting for PostgreSQL to be ready..."
timeout 60 bash -c 'until pg_isready -h localhost -p 5432 -U postgres; do sleep 1; done' timeout 60 bash -c 'until docker exec ci-dind docker exec ci-cd-test-postgres pg_isready -h localhost -p 5432 -U postgres; do sleep 1; done'
# Create test database if it doesn't exist # Create test database if it doesn't exist
docker exec ci-cd-test-rust sqlx database create --database-url "$DATABASE_URL" || true docker exec ci-dind docker exec ci-cd-test-rust sqlx database create --database-url "$DATABASE_URL" || true
# Run initial migrations to set up the database # Run initial migrations to set up the database
docker exec ci-cd-test-rust sqlx migrate run --database-url "$DATABASE_URL" || true docker exec ci-dind docker exec ci-cd-test-rust sqlx migrate run --database-url "$DATABASE_URL" || true
# Validate migration files # Validate migration files
docker exec ci-cd-test-rust ./scripts/validate_migrations.sh --verbose docker exec ci-dind docker exec ci-cd-test-rust ./scripts/validate_migrations.sh --verbose
- name: Run backend tests - name: Run backend tests
working-directory: ./backend working-directory: ./backend
@ -60,27 +91,27 @@ jobs:
DATABASE_URL: postgres://postgres:postgres@localhost:5432/sharenet_test DATABASE_URL: postgres://postgres:postgres@localhost:5432/sharenet_test
run: | run: |
# Run tests with increased parallelism for Rust # Run tests with increased parallelism for Rust
docker exec ci-cd-test-rust cargo test --all --jobs 4 docker exec ci-dind docker exec ci-cd-test-rust cargo test --all --jobs 4
docker exec ci-cd-test-rust cargo clippy --all -- -D warnings docker exec ci-dind docker exec ci-cd-test-rust cargo clippy --all -- -D warnings
docker exec ci-cd-test-rust cargo fmt --all -- --check docker exec ci-dind docker exec ci-cd-test-rust cargo fmt --all -- --check
- name: Install frontend dependencies - name: Install frontend dependencies
run: | run: |
docker exec ci-cd-test-node npm ci docker exec ci-dind docker exec ci-cd-test-node npm ci
- name: Run frontend tests - name: Run frontend tests
run: | run: |
docker exec ci-cd-test-node npm run lint docker exec ci-dind docker exec ci-cd-test-node npm run lint
docker exec ci-cd-test-node npm run type-check docker exec ci-dind docker exec ci-cd-test-node npm run type-check
docker exec ci-cd-test-node npm run build docker exec ci-dind docker exec ci-cd-test-node npm run build
- name: Cleanup Containerized Environment - name: Cleanup Testing Environment
if: always() if: always()
run: | run: |
# Stop and remove all testing containers # Stop and remove all testing containers (but keep DinD running)
docker compose -f docker-compose.test.yml down docker exec ci-dind docker compose -f /workspace/docker-compose.test.yml down
# Job 2: Building - Uses DinD for isolated image building and pushing # Job 2: Building - Build and push Docker images using same DinD
build-and-push: build-and-push:
name: Build and Push Docker Images (DinD) name: Build and Push Docker Images (DinD)
needs: [test] needs: [test]
@ -91,61 +122,54 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup DinD environment - name: Set up Docker Buildx in DinD
run: | run: |
# Start DinD container for isolated Docker operations # Set up Docker Buildx inside the existing DinD container
docker run -d \ docker exec ci-dind docker buildx create --use --name ci-builder || true
--name ci-cd-build-dind \ docker exec ci-dind docker buildx inspect --bootstrap
--privileged \
-p 2375:2375 \
-e DOCKER_TLS_CERTDIR="" \
docker:dind
# Wait for DinD to be ready
echo "Waiting for DinD container to be ready..."
timeout 60 bash -c 'until docker exec ci-cd-build-dind docker version; do sleep 2; done'
# Configure Docker for Harbor registry (needed for pushing images)
docker exec ci-cd-build-dind sh -c 'echo "{\"insecure-registries\": [\"${{ secrets.CI_HOST }}:5000\"]}" > /etc/docker/daemon.json'
docker exec ci-cd-build-dind sh -c 'kill -HUP 1'
# Wait for Docker daemon to reload
sleep 5
# Verify DinD is working
docker exec ci-cd-build-dind docker version
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push backend image - name: Build and push backend image
uses: docker/build-push-action@v5 run: |
with: # Build and push backend image using DinD
context: ./backend docker exec ci-dind docker buildx build \
file: ./backend/Dockerfile --platform linux/amd64 \
push: true --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/backend:${{ github.sha }} \
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/backend:${{ github.sha }} --push \
cache-from: type=gha --cache-from type=gha \
cache-to: type=gha,mode=max --cache-to type=gha,mode=max \
platforms: linux/amd64 -f ./backend/Dockerfile \
./backend
- name: Build and push frontend image - name: Build and push frontend image
uses: docker/build-push-action@v5 run: |
with: # Build and push frontend image using DinD
context: ./frontend docker exec ci-dind docker buildx build \
file: ./frontend/Dockerfile --platform linux/amd64 \
push: true --tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/frontend:${{ github.sha }} \
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/frontend:${{ github.sha }} --push \
cache-from: type=gha --cache-from type=gha \
cache-to: type=gha,mode=max --cache-to type=gha,mode=max \
platforms: linux/amd64 -f ./frontend/Dockerfile \
./frontend
- name: Cleanup DinD environment - name: Cleanup Testing Environment
if: always() if: always()
run: | run: |
# Stop and remove DinD container # Clean up test containers but keep DinD running for reuse
docker stop ci-cd-build-dind || true docker exec ci-dind docker system prune -f || true
docker rm ci-cd-build-dind || true
# Check if DinD needs restart due to resource accumulation
DISK_USAGE=$(docker exec ci-dind df -h /var/lib/docker 2>/dev/null | tail -1 | awk '{print $5}' | sed 's/%//' || echo "0")
echo "DinD disk usage: ${DISK_USAGE}%"
# Restart DinD if disk usage is high (>80%)
if [ "$DISK_USAGE" -gt 80 ]; then
echo "WARNING: High disk usage (${DISK_USAGE}%), restarting DinD container..."
docker restart ci-dind
echo "DinD container restarted"
else
echo "Disk usage acceptable (${DISK_USAGE}%), keeping DinD running"
fi
# Job 3: Deployment - Runs directly on production runner (no DinD needed) # Job 3: Deployment - Runs directly on production runner (no DinD needed)
deploy: deploy:
@ -177,7 +201,7 @@ jobs:
- name: Configure Docker for Harbor access - name: Configure Docker for Harbor access
run: | run: |
# Configure Docker to access Harbor registry on CI Linode # Configure Docker to access Harbor registry on CI Linode
echo '{"insecure-registries": ["${{ secrets.CI_HOST }}:5000"]}' | sudo tee /etc/docker/daemon.json echo '{"insecure-registries": ["${{ secrets.CI_HOST }}:80"]}' | sudo tee /etc/docker/daemon.json
sudo systemctl restart docker sudo systemctl restart docker
# Wait for Docker to be ready # Wait for Docker to be ready

View file

@ -478,7 +478,23 @@ sudo usermod -aG docker SERVICE_USER
### Step 5: Set Up Harbor Container Registry ### Step 5: Set Up Harbor Container Registry
#### 5.1 Generate SSL Certificates Harbor provides a secure, enterprise-grade container registry with vulnerability scanning, role-based access control, and audit logging.
#### 5.1 Create Harbor 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
# Set secure password for emergency access
echo "harbor:$(openssl rand -base64 32)" | sudo chpasswd
# Add harbor user to docker group
sudo usermod -aG docker harbor
```
#### 5.2 Generate SSL Certificates
```bash ```bash
# Create system SSL directory for Harbor certificates # Create system SSL directory for Harbor certificates
@ -516,20 +532,15 @@ EOF
# Generate self-signed certificate with proper SANs # 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 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 # 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 600 /etc/ssl/registry/registry.key
sudo chmod 644 /etc/ssl/registry/registry.crt sudo chmod 644 /etc/ssl/registry/registry.crt
sudo chmod 644 /etc/ssl/registry/openssl.conf sudo chmod 644 /etc/ssl/registry/openssl.conf
``` ```
**Important**: The certificate is now generated with proper Subject Alternative Names (SANs) including your IP address, which is required for TLS certificate validation by Docker and other clients. #### 5.3 Configure Docker to Trust Harbor Registry
**Note**: The permissions are set to:
- `registry.key`: `600` (owner read/write only) - private key must be secure
- `registry.crt`: `644` (owner read/write, group/others read) - certificate can be read by services
- `openssl.conf`: `644` (owner read/write, group/others read) - configuration file for reference
#### 5.1.1 Configure Docker to Trust Harbor Registry
```bash ```bash
# Add the certificate to system CA certificates # Add the certificate to system CA certificates
@ -549,301 +560,126 @@ EOF
sudo systemctl restart docker sudo systemctl restart docker
``` ```
**Important**: Replace `YOUR_CI_CD_IP` with your actual CI/CD Linode IP address. This configuration tells Docker to trust your Harbor registry and allows Docker login to work properly. **Important**: Replace `YOUR_CI_CD_IP` with your actual CI/CD Linode IP address.
#### 5.2 Generate Secure Passwords and Secrets #### 5.4 Install Harbor
```bash ```bash
# Set environment variables for Harbor # Switch to harbor user
sudo su - harbor
# Set environment variables
export HARBOR_HOSTNAME=$YOUR_ACTUAL_IP export HARBOR_HOSTNAME=$YOUR_ACTUAL_IP
export HARBOR_ADMIN_PASSWORD="Harbor12345" export HARBOR_ADMIN_PASSWORD="Harbor12345"
# Generate secure database password for Harbor
export DB_PASSWORD=$(openssl rand -base64 32 | tr -d "=+/" | cut -c1-25) export DB_PASSWORD=$(openssl rand -base64 32 | tr -d "=+/" | cut -c1-25)
# Generate secure secrets for Harbor
export CORE_SECRET=$(openssl rand -hex 16) export CORE_SECRET=$(openssl rand -hex 16)
export JOBSERVICE_SECRET=$(openssl rand -hex 16) export JOBSERVICE_SECRET=$(openssl rand -hex 16)
echo "Generated secrets:" # Download and install Harbor
echo "DB_PASSWORD: $DB_PASSWORD" cd /opt/harbor
echo "CORE_SECRET: $CORE_SECRET" wget https://github.com/goharbor/harbor/releases/download/v2.10.0/harbor-offline-installer-v2.10.0.tgz
echo "JOBSERVICE_SECRET: $JOBSERVICE_SECRET" tar -xzf harbor-offline-installer-v2.10.0.tgz
# Save secrets securely for future reference
cat > /opt/APP_NAME/harbor-secrets.txt << EOF
# Harbor Secrets - KEEP THESE SECURE!
# Generated on: $(date)
# CI/CD IP: $YOUR_ACTUAL_IP
HARBOR_HOSTNAME=$HARBOR_HOSTNAME
HARBOR_ADMIN_PASSWORD=$HARBOR_ADMIN_PASSWORD
DB_PASSWORD=$DB_PASSWORD
CORE_SECRET=$CORE_SECRET
JOBSERVICE_SECRET=$JOBSERVICE_SECRET
# IMPORTANT: Store this file securely and keep a backup!
# You will need these secrets for:
# - Harbor upgrades
# - Database troubleshooting
# - Disaster recovery
# - Service restoration
EOF
# Set secure permissions on secrets file
chmod 600 /opt/APP_NAME/harbor-secrets.txt
echo "Secrets saved to /opt/APP_NAME/harbor-secrets.txt"
echo "IMPORTANT: Keep this file secure and backed up!"
```
**Important**:
- Change the default passwords for production use. The default admin password is `Harbor12345` - change this immediately after first login.
- The generated secrets (`CORE_SECRET` and `JOBSERVICE_SECRET`) are cryptographically secure random values used for encrypting sensitive data.
- Store these secrets securely as they will be needed for Harbor upgrades or troubleshooting.
- **CRITICAL**: The secrets file contains sensitive information. Keep it secure and backed up!
#### 5.3 Install Harbor Using Official Installer
```bash
# Switch to DEPLOY_USER (who has sudo access)
sudo su - DEPLOY_USER
cd /opt/APP_NAME
# Download Harbor 2.10.0 offline installer
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 cd harbor
sudo cp harbor.yml.tmpl harbor.yml cp harbor.yml.tmpl harbor.yml
# Edit harbor.yml configuration # Edit harbor.yml configuration
sudo nano harbor.yml nano harbor.yml
``` ```
**Important**: In the `harbor.yml` file, update the following variables: **Important**: In the `harbor.yml` file, update:
- `hostname: YOUR_CI_CD_IP` (replace with your actual IP) - `hostname: YOUR_CI_CD_IP` (replace with your actual IP)
- `certificate: /etc/ssl/registry/registry.crt` - `certificate: /etc/ssl/registry/registry.crt`
- `private_key: /etc/ssl/registry/registry.key` - `private_key: /etc/ssl/registry/registry.key`
- `password: <the DB_PASSWORD generated in Step 5.2>` - `password: <the DB_PASSWORD generated above>`
**Note**: Leave `harbor_admin_password` as `Harbor12345` for now. This will be changed at first login through the UI after launching Harbor.
#### 5.4 Prepare and Install Harbor
```bash ```bash
# Prepare Harbor configuration
sudo ./prepare
# Install Harbor with Trivy vulnerability scanner # Install Harbor with Trivy vulnerability scanner
sudo ./install.sh --with-trivy ./prepare
cd ..
# Change harbor folder permissions recursively to SERVICE_USER
sudo chown -R SERVICE_USER:SERVICE_USER harbor
# Switch to SERVICE_USER to run installation again as non-root
sudo su - SERVICE_USER
cd /opt/APP_NAME/harbor
# Install Harbor as SERVICE_USER (permissions are partially adjusted correctly)
./install.sh --with-trivy ./install.sh --with-trivy
# Exit SERVICE_USER shell # Exit harbor user shell
exit exit
``` ```
#### 5.5 Fix Permission Issues #### 5.5 Create Systemd Service
```bash ```bash
# Switch back to DEPLOY_USER to adjust the permissions for various env files # Create systemd service file for Harbor
cd /opt/APP_NAME/harbor sudo tee /etc/systemd/system/harbor.service << EOF
[Unit]
Description=Harbor Container Registry
After=docker.service
Requires=docker.service
sudo chown SERVICE_USER:SERVICE_USER common/config/jobservice/env [Service]
sudo chown SERVICE_USER:SERVICE_USER common/config/db/env Type=oneshot
sudo chown SERVICE_USER:SERVICE_USER common/config/registryctl/env RemainAfterExit=yes
sudo chown SERVICE_USER:SERVICE_USER common/config/trivy-adapter/env User=harbor
sudo chown SERVICE_USER:SERVICE_USER common/config/core/env Group=harbor
WorkingDirectory=/opt/harbor/harbor
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
# Exit DEPLOY_USER shell [Install]
exit WantedBy=multi-user.target
EOF
# Enable and start Harbor service
sudo systemctl daemon-reload
sudo systemctl enable harbor.service
sudo systemctl start harbor.service
# Monitor startup (can take 2-3 minutes)
sudo journalctl -u harbor.service -f
``` ```
#### 5.6 Test Harbor Installation #### 5.6 Configure Harbor 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`
#### 5.7 Test Harbor Setup
```bash ```bash
# Switch to SERVICE_USER # Switch to DEPLOY_USER for testing
sudo su - SERVICE_USER sudo su - DEPLOY_USER
cd /opt/APP_NAME/harbor # Test Docker login and push
docker login YOUR_CI_CD_IP:80 -u ci-user -p "your-secure-password"
# Verify you can stop Harbor. All Harbor containers should stop. # Create and push test image
docker compose down
# Verify you can bring Harbor back up. All Harbor containers should start back up.
docker compose up -d
# Exit SERVICE_USER shell
exit
```
**Important**: Harbor startup can take 2-3 minutes as it initializes the database and downloads vulnerability databases. The health check will ensure all services are running properly.
#### 5.7 Wait for Harbor Startup
```bash
# Monitor Harbor startup progress
cd /opt/APP_NAME/harbor
docker compose logs -f
```
**Expected output**: You should see logs from all Harbor services (core, database, redis, registry, portal, nginx, jobservice, trivy) starting up. Wait until you see "Harbor has been installed and started successfully" or similar success messages.
#### 5.8 Test Harbor Setup
```bash
# Check if all Harbor containers are running
cd /opt/APP_NAME/harbor
docker compose ps
# Test Harbor API (HTTPS)
curl -k https://localhost/api/v2.0/health
# Test Harbor UI (HTTPS)
curl -k -I https://localhost
# Expected output: HTTP/1.1 200 OK
```
**Important**: All Harbor services should show as "Up" in the `docker compose ps` output. The health check should return a JSON response indicating all services are healthy.
#### 5.9 Access Harbor Web UI
1. **Open your browser** and navigate to: `https://YOUR_CI_CD_IP`
2. **Login with default credentials**:
- Username: `admin`
- Password: `Harbor12345` (or your configured password)
3. **Change the admin password**:
- Click on the user icon "admin" in the top right corner of the UI
- Click "Change Password" from the dropdown menu
- Enter your current password and a new secure password
- Click "OK" to save the new password
#### 5.10 Configure Harbor for Public Read, Authenticated Write
1. **Create Application Project**:
- Go to **Projects** → **New Project**
- Set **Project Name**: `APP_NAME` (replace with your actual application name)
- Set **Access Level**: `Public`
- Click **OK**
2. **Create a User for CI/CD**:
- Go to **Administration****Users** → **New User**
- Set **Username**: `ci-user`
- Set **Email**: `ci@example.com`
- Set **Password**: `your-secure-password`
- Click **OK**
3. **Assign Project Role to ci-user**:
- Go to **Projects****APP_NAME****Members****+ User**
- Select **User**: `ci-user`
- Set **Role**: `Developer`
- Click **OK**
**Note**: With a public project, anyone can pull images without authentication, but only authenticated users (like `ci-user`) can push images. This provides the perfect balance of ease of use for deployments and security for image management.
#### 5.11 Test Harbor Authentication and Access Model
```bash
# Test Docker login to Harbor
docker login YOUR_CI_CD_IP
# Enter: ci-user and your-secure-password
# Create a test image
echo "FROM alpine:latest" > /tmp/test.Dockerfile echo "FROM alpine:latest" > /tmp/test.Dockerfile
echo "RUN echo 'Hello from Harbor test image'" >> /tmp/test.Dockerfile docker build -f /tmp/test.Dockerfile -t YOUR_CI_CD_IP:80/APP_NAME/test:latest /tmp
docker push YOUR_CI_CD_IP:80/APP_NAME/test:latest
# Build and tag test image for APP_NAME project # Test public pull (no authentication)
docker build -f /tmp/test.Dockerfile -t YOUR_CI_CD_IP/APP_NAME/test:latest /tmp docker logout YOUR_CI_CD_IP:80
docker pull YOUR_CI_CD_IP:80/APP_NAME/test:latest
# Push to Harbor (requires authentication)
docker push YOUR_CI_CD_IP/APP_NAME/test:latest
# Test public pull (no authentication required)
docker logout YOUR_CI_CD_IP
docker pull YOUR_CI_CD_IP/APP_NAME/test:latest
# Verify the image was pulled successfully
docker images | grep APP_NAME/test
# Test that unauthorized push is blocked # Test that unauthorized push is blocked
echo "FROM alpine:latest" > /tmp/unauthorized.Dockerfile echo "FROM alpine:latest" > /tmp/unauthorized.Dockerfile
echo "RUN echo 'This push should fail'" >> /tmp/unauthorized.Dockerfile docker build -f /tmp/unauthorized.Dockerfile -t YOUR_CI_CD_IP:80/APP_NAME/unauthorized:latest /tmp
docker build -f /tmp/unauthorized.Dockerfile -t YOUR_CI_CD_IP/APP_NAME/unauthorized:latest /tmp docker push YOUR_CI_CD_IP:80/APP_NAME/unauthorized:latest
docker push YOUR_CI_CD_IP/APP_NAME/unauthorized:latest
# Expected: This should fail with authentication error # Expected: This should fail with authentication error
# Clean up test images # Clean up
docker rmi YOUR_CI_CD_IP/APP_NAME/test:latest docker rmi YOUR_CI_CD_IP:80/APP_NAME/test:latest
docker rmi YOUR_CI_CD_IP/APP_NAME/unauthorized:latest docker rmi YOUR_CI_CD_IP:80/APP_NAME/unauthorized:latest
exit
``` ```
**Expected behavior**: **Expected behavior**:
- ✅ **Push requires authentication**: `docker push` only works when logged in - ✅ Push requires authentication
- ✅ **Pull works without authentication**: `docker pull` works without login for public projects - ✅ Pull works without authentication
- ✅ **Unauthorized push is blocked**: `docker push` fails when not logged in - ✅ Unauthorized push is blocked
- ✅ **Web UI accessible**: Harbor UI is available at `https://YOUR_CI_CD_IP` - ✅ Web UI accessible at `https://YOUR_CI_CD_IP`
#### 5.12 Harbor Access Model Summary
Your Harbor registry is now configured with the following access model:
**APP_NAME Project**:
- ✅ **Pull (read)**: No authentication required
- ✅ **Push (write)**: Requires authentication
- ✅ **Web UI**: Accessible to view images
**Security Features**:
- ✅ **Vulnerability scanning**: Automatic CVE scanning with Trivy
- ✅ **Role-based access control**: Different user roles (admin, developer, guest)
- ✅ **Audit logs**: Complete trail of all operations
#### 5.13 Troubleshooting Common Harbor Issues
**Certificate Issues**:
```bash
# If you get "tls: failed to verify certificate" errors:
# 1. Verify certificate has proper SANs
openssl x509 -in /etc/ssl/registry/registry.crt -text -noout | grep -A 5 "Subject Alternative Name"
# 2. Regenerate certificate if SANs are missing
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
# 3. Restart Harbor and Docker
cd /opt/APP_NAME/harbor && docker compose down && docker compose up -d
sudo systemctl restart docker
```
**Connection Issues**:
```bash
# If you get "connection refused" errors:
# 1. Check if Harbor is running
docker compose ps
# 2. Check Harbor logs
docker compose logs
# 3. Verify ports are open
netstat -tuln | grep -E ':(80|443)'
```
**Docker Configuration Issues**:
```bash
# If Docker still can't connect after certificate fixes:
# 1. Verify Docker daemon configuration
cat /etc/docker/daemon.json cat /etc/docker/daemon.json
# 2. Check if certificate is in system CA store # 2. Check if certificate is in system CA store
@ -1147,9 +983,7 @@ sudo docker run -d \
-e DOCKER_TLS_CERTDIR="" \ -e DOCKER_TLS_CERTDIR="" \
docker:dind docker:dind
# Wait for DinD to be ready # Wait for a minute or two for DinD to be ready (wait for Docker daemon inside DinD)
echo "Waiting for DinD container to be ready..."
timeout 60 bash -c 'until sudo docker exec ci-dind docker version; do sleep 2; done'
# Test DinD connectivity # Test DinD connectivity
sudo docker exec ci-dind docker version sudo docker exec ci-dind docker version
@ -1169,22 +1003,17 @@ sudo docker exec ci-dind docker version
# Navigate to the application directory # Navigate to the application directory
cd /opt/APP_NAME cd /opt/APP_NAME
# Configure Docker daemon in DinD for Harbor registry # Copy Harbor certificate to DinD container
sudo docker exec ci-dind sh -c 'echo "{\"insecure-registries\": [\"localhost:5000\"]}" > /etc/docker/daemon.json' sudo docker cp /etc/ssl/registry/registry.crt ci-dind:/usr/local/share/ca-certificates/
sudo docker exec ci-dind update-ca-certificates
# Reload Docker daemon in DinD # Test Harbor connectivity from DinD (using certificate trust)
sudo docker exec ci-dind sh -c 'kill -HUP 1'
# Wait for Docker daemon to reload
sleep 5
# Test Harbor connectivity from DinD
sudo docker exec ci-dind docker pull alpine:latest sudo docker exec ci-dind docker pull alpine:latest
sudo docker exec ci-dind docker tag alpine:latest localhost:5000/test/alpine:latest sudo docker exec ci-dind docker tag alpine:latest YOUR_CI_CD_IP:80/test/alpine:latest
sudo docker exec ci-dind docker push localhost:5000/test/alpine:latest sudo docker exec ci-dind docker push YOUR_CI_CD_IP:80/test/alpine:latest
# Clean up test image # Clean up test image
sudo docker exec ci-dind docker rmi localhost:5000/test/alpine:latest sudo docker exec ci-dind docker rmi YOUR_CI_CD_IP:80/test/alpine:latest
``` ```
**What this does**: **What this does**:
@ -1204,16 +1033,18 @@ The CI/CD pipeline uses a three-stage approach with dedicated environments for e
- Rust toolchain for backend testing and migrations - Rust toolchain for backend testing and migrations
- Node.js toolchain for frontend testing - Node.js toolchain for frontend testing
- **Network**: All containers communicate through `ci-cd-test-network` - **Network**: All containers communicate through `ci-cd-test-network`
- **Cleanup**: `docker compose -f docker-compose.test.yml down` - **Setup**: DinD container created, Harbor certificate installed, Docker login performed
- **Cleanup**: Testing containers removed, DinD container kept running
**Job 2 (Building) - Direct Docker Commands:** **Job 2 (Building) - Direct Docker Commands:**
- **Purpose**: Isolated image building and pushing to Harbor - **Purpose**: Image building and pushing to Harbor
- **Environment**: Single DinD container for Docker operations - **Environment**: Same DinD container from Job 1
- **Process**: - **Process**:
- Uses Docker Buildx for efficient building - Uses Docker Buildx for efficient building
- Builds backend and frontend images separately - Builds backend and frontend images separately
- Pushes images to Harbor registry - Pushes images to Harbor registry
- **Cleanup**: Simple container stop/remove - **Harbor Access**: Reuses Harbor authentication from Job 1
- **Cleanup**: DinD container stopped and removed (clean slate for next run)
**Job 3 (Deployment) - `docker-compose.prod.yml`:** **Job 3 (Deployment) - `docker-compose.prod.yml`:**
- **Purpose**: Production deployment with pre-built images - **Purpose**: Production deployment with pre-built images
@ -1239,11 +1070,11 @@ docker exec ci-dind docker run --rm alpine:latest echo "DinD is working!"
# Test Harbor integration # Test Harbor integration
docker exec ci-dind docker pull alpine:latest docker exec ci-dind docker pull alpine:latest
docker exec ci-dind docker tag alpine:latest localhost:5000/test/dind-test:latest docker exec ci-dind docker tag alpine:latest YOUR_CI_CD_IP:80/test/dind-test:latest
docker exec ci-dind docker push localhost:5000/test/dind-test:latest docker exec ci-dind docker push YOUR_CI_CD_IP:80/test/dind-test:latest
# Clean up test # Clean up test
docker exec ci-dind docker rmi localhost:5000/test/dind-test:latest docker exec ci-dind docker rmi YOUR_CI_CD_IP:80/test/dind-test:latest
``` ```
**Expected Output**: **Expected Output**:
@ -1800,12 +1631,14 @@ curl http://localhost:3001/health
Go to your Forgejo repository and add these secrets in **Settings → Secrets and Variables → Actions**: Go to your Forgejo repository and add these secrets in **Settings → Secrets and Variables → Actions**:
**Required Secrets:** **Required Secrets:**
- `CI_CD_IP`: Your CI/CD Linode IP address - `CI_HOST`: Your CI/CD Linode IP address (used for Harbor registry access)
- `PRODUCTION_IP`: Your Production Linode IP address - `PRODUCTION_IP`: Your Production Linode IP address
- `DEPLOY_USER`: The deployment user name (e.g., `deploy`, `ci`, `admin`) - `DEPLOY_USER`: The deployment user name (e.g., `deploy`, `ci`, `admin`)
- `SERVICE_USER`: The service user name (e.g., `appuser`, `service`, `app`) - `SERVICE_USER`: The service user name (e.g., `appuser`, `service`, `app`)
- `APP_NAME`: Your application name (e.g., `sharenet`, `myapp`) - `APP_NAME`: Your application name (e.g., `sharenet`, `myapp`)
- `POSTGRES_PASSWORD`: A strong password for the PostgreSQL database - `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)
**Optional Secrets (for domain users):** **Optional Secrets (for domain users):**
- `DOMAIN`: Your domain name (e.g., `example.com`) - `DOMAIN`: Your domain name (e.g., `example.com`)