Docker Image Build and Deployment Workflow

This document describes the standardized Docker image build, versioning, and deployment workflow used in the argocd-eda project. This pattern can be followed by other projects for consistent container lifecycle management.

Overview

The argocd-eda project implements a comprehensive Docker image workflow that includes:

  • Local Development: Manual build and push for rapid iteration
  • Automated CI/CD: Building via GitHub Actions
  • Semantic Versioning: Automatic version bumping
  • Multi-architecture Support: AMD64/ARM64 builds
  • Security Scanning: Trivy vulnerability scanning
  • Registry Integration: GitHub Container Registry (GHCR)
  • Image Factory: Centralized lifecycle management
  • GitOps Deployment: Kargo promotion pipelines

Workflow Components

1. Local Development Workflow

For rapid development and testing, developers can build and push images locally before triggering automated CI/CD workflows.

Prerequisites

  1. Docker Authentication: Log in to GitHub Container Registry
echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin
  1. Multi-platform Support: Install Docker Buildx for multi-architecture builds
docker buildx create --use --name multiarch

Local Build and Push Process

Local Testing (No Push):

# Build for current platform only (fast local testing)
docker build -t app-name:local .
 
# Run the application
docker run --rm -it -p 8000:8000 app-name:local
 
# Get shell access for debugging
docker run --rm -it --entrypoint /bin/bash app-name:local

Multi-Architecture Build and Push:

# Build and push for multiple platforms to GHCR
docker buildx build \
  --platform linux/amd64,linux/arm64 \
  --tag ghcr.io/owner/app-name:$(git rev-parse --short HEAD)-dev \
  --push .

Common Build Tasks (Workspace-Shared)

Recommendation: Yes, create common build tasks in workspace-shared for consistency across projects.

File: libs/workspace-shared/Taskfile.yml

version: '3'
 
vars:
  REGISTRY: ghcr.io
  OWNER: '{{.OWNER | default "craigedmunds"}}'
  
tasks:
  docker:build:local:
    desc: Build Docker image for local testing (no push)
    vars:
      APP_NAME: '{{.APP_NAME}}'
      DOCKERFILE: '{{.DOCKERFILE}}'
      CONTEXT: '{{.CONTEXT}}'
    cmds:
      - docker build -f {{.DOCKERFILE}} -t {{.APP_NAME}}:local {{.CONTEXT}}
    
  docker:run:
    desc: Run Docker container from local image
    vars:
      APP_NAME: '{{.APP_NAME}}'
      PORTS: '{{.PORTS}}'
      ENV_VARS: '{{.ENV_VARS}}'
      VOLUMES: '{{.VOLUMES}}'
    cmds:
      - docker run --rm -it {{.PORTS}} {{.ENV_VARS}} {{.VOLUMES}} {{.APP_NAME}}:local
    
  docker:shell:
    desc: Get shell access to Docker container
    vars:
      APP_NAME: '{{.APP_NAME}}'
      SHELL: '{{.SHELL | default "/bin/bash"}}'
      VOLUMES: '{{.VOLUMES}}'
    cmds:
      - docker run --rm -it {{.VOLUMES}} --entrypoint {{.SHELL}} {{.APP_NAME}}:local
    
  docker:build:multiarch:
    desc: Build and push multi-architecture Docker image
    vars:
      APP_NAME: '{{.APP_NAME}}'
      DOCKERFILE: '{{.DOCKERFILE}}'
      CONTEXT: '{{.CONTEXT}}'
      TAG: '{{.TAG | default (printf "%s-dev" (exec "git" "rev-parse" "--short" "HEAD"))}}'
      PLATFORMS: '{{.PLATFORMS | default "linux/amd64,linux/arm64"}}'
    cmds:
      - docker buildx build --platform {{.PLATFORMS}} -f {{.DOCKERFILE}} -t {{.REGISTRY}}/{{.OWNER}}/{{.APP_NAME}}:{{.TAG}} --push {{.CONTEXT}}
    
  docker:scan:
    desc: Scan Docker image for vulnerabilities with Trivy
    vars:
      APP_NAME: '{{.APP_NAME}}'
      TAG: '{{.TAG | default (printf "%s-dev" (exec "git" "rev-parse" "--short" "HEAD"))}}'
    cmds:
      - trivy image {{.REGISTRY}}/{{.OWNER}}/{{.APP_NAME}}:{{.TAG}}

Project-Specific Task Integration

Multiple Apps Per Repo - Include with Prefixes:

version: '3'
 
includes:
  gateway: 
    taskfile: ../libs/workspace-shared/Taskfile.yml
    vars:
      APP_NAME: mobile-ai-gateway
      DOCKERFILE: services/gateway/Dockerfile
      CONTEXT: services/gateway
      PORTS: "-p 8000:8000"
      ENV_VARS: "-e ENV=development"
      VOLUMES: "-v $(pwd)/config:/app/config"
  
  worker:
    taskfile: ../libs/workspace-shared/Taskfile.yml
    vars:
      APP_NAME: mobile-ai-worker
      DOCKERFILE: services/worker/Dockerfile
      CONTEXT: services/worker
      ENV_VARS: "-e ENV=development"
      VOLUMES: "-v $(pwd)/logs:/app/logs"
 
# Now you can call:
# task gateway:docker:build:local
# task gateway:docker:run
# task worker:docker:build:local
# task worker:docker:run

Development Workflow Integration

Local Development → GitHub Actions Pipeline:

  1. Local Development:
# Gateway service
task gateway:docker:build:local
task gateway:docker:run
task gateway:docker:shell
 
# Worker service
task worker:docker:build:local
task worker:docker:run
task worker:docker:shell
 
# When ready, build and push multi-arch for integration testing
task gateway:docker:build:multiarch
task worker:docker:build:multiarch
  1. Trigger GitHub Actions:
# Push to feature branch triggers draft build
git push origin feature/new-feature
 
# Push to main triggers production build
git push origin main

2. Reusable Docker Build Workflow

File: .github/workflows/_docker-build.yml

This is a reusable workflow that standardizes the Docker build process across all applications. It provides:

Key Features:

  • Semantic Versioning: Automatic version bumping (major/minor/patch)
  • Branch-based Tagging: Feature branches get -draft-{branch} suffix
  • Multi-platform Builds: AMD64 and ARM64 support
  • Security Scanning: Trivy vulnerability scanning
  • Registry Integration: GitHub Container Registry (GHCR)
  • Automated Releases: GitHub releases with image metadata

Version Management:

  • File-based: Simple VERSION file (e.g., 0.2.0)
  • Package.json-based: For Node.js applications
  • Auto-detection: Commit message parsing for version bump type
    • [major] or breaking change → major bump
    • [minor] or feat: → minor bump
    • Default → patch bump

Branch Strategy:

  • Main branch: Production builds with latest tag
  • Feature branches: Draft builds with branch suffix
  • Version commits: Automatic version file updates on main

3. Application-Specific Workflows

Each application has its own workflow that calls the reusable build workflow:

Example: UV Service (uv.yml)

jobs:
  build-and-push:
    uses: ./.github/workflows/_docker-build.yml
    with:
      app_name: 'UV'
      image_name: 'craigedmunds/uv'
      dockerfile: 'apps/uv/Dockerfile'
      context: 'apps/uv'
      version_file: 'apps/uv/VERSION'
      version_type: 'file'

Example: Backstage (backstage.yml)

jobs:
  build-and-push:
    uses: ./.github/workflows/_docker-build.yml
    with:
      app_name: 'Backstage'
      image_name: 'craigedmunds/backstage'
      dockerfile: 'backstage/app/packages/backend/Dockerfile'
      context: 'backstage/app'
      version_file: 'backstage/app/package.json'
      version_type: 'package-json'
      pre_build_script: |
        cd backstage/app
        cp .yarnrc.ci.yml .yarnrc.yml

4. Image Factory Integration

File: image-factory/images.yaml

The Image Factory provides centralized container lifecycle management:

Image Registration:

- name: backstage
  registry: ghcr.io
  repository: craigedmunds/backstage
  source:
    provider: github
    repo: craigedmunds/argocd-eda
    branch: main
    dockerfile: backstage/app/packages/backend/Dockerfile
    workflow: backstage.yml
  rebuildDelay: 7d
  autoRebuild: true

Key Features:

  • Dependency Tracking: Monitors base image updates
  • Automated Rebuilds: Triggers rebuilds when dependencies change
  • State Management: Centralized image configuration
  • Kargo Integration: GitOps deployment pipelines

Implementation Guide

Step 0: Set Up Local Development Environment

Colima is a lightweight alternative to Docker Desktop that uses minimal resources and provides excellent multi-architecture build support on macOS.

Installation:

# Install Colima via Homebrew
brew install colima docker docker-buildx
 
# Start Colima with optimized settings
colima start --cpu 4 --memory 4 --arch aarch64 --vm-type=vz --vz-rosetta

Configuration:

  • --cpu 4 --memory 4: Allocate 4 CPUs and 4GB RAM (adjust based on your needs)
  • --arch aarch64: Use ARM64 architecture (native on Apple Silicon)
  • --vm-type=vz: Use Apple’s Virtualization framework (faster than QEMU)
  • --vz-rosetta: Enable Rosetta for x86_64 emulation (required for multi-arch builds)

Verify Setup:

# Check Colima status
colima status
 
# Verify Docker is working
docker ps
 
# Verify buildx is available
docker buildx ls

Multi-Architecture Builds: Colima with Rosetta provides excellent multi-arch build performance:

# Build for both AMD64 and ARM64
docker buildx build --platform linux/amd64,linux/arm64 -t myimage:latest .

Benefits:

  • Lightweight: Uses significantly less resources than Docker Desktop
  • Fast: Native Apple Virtualization framework
  • Free: No licensing concerns
  • Multi-arch: Excellent support for AMD64/ARM64 builds via Rosetta

Managing Colima:

# Stop Colima
colima stop
 
# Restart Colima
colima restart
 
# Delete Colima VM (clean slate)
colima delete

If you need Docker Desktop’s additional features:

Prerequisites:

  1. Install Docker Desktop: Download from docker.com
  2. Enable Buildx: Already included in Docker Desktop

Create Multi-Platform Builder:

docker buildx create --use --name multiarch

Common Setup Steps (Both Options)

  1. Configure GitHub Container Registry Access:
# Create GitHub Personal Access Token with packages:write scope
# Then authenticate
echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin
  1. Set Up Workspace-Shared Tasks:
# Create shared Taskfile if it doesn't exist
mkdir -p libs/workspace-shared
# Copy the common build tasks from the example above

Step 1: Create Reusable Build Workflow

  1. Copy the reusable workflow: Use _docker-build.yml as a template
  2. Customize parameters: Adjust registry, security scanning, etc.
  3. Configure secrets: Set up GHCR_TOKEN and SUBMODULE_PAT

Step 2: Create Application Workflows

For each application that needs Docker images:

  1. Create workflow file: .github/workflows/{app-name}.yml
  2. Configure triggers: Set up path-based triggers
  3. Call reusable workflow: Pass application-specific parameters
  4. Add testing: Include pre-build validation (kustomize tests, unit tests)

Workflow Template:

name: Build and Push {App Name}
 
on:
  push:
    branches: [main]
    paths:
      - 'path/to/app/**'
      - '.github/workflows/{app-name}.yml'
      - '.github/workflows/_docker-build.yml'
  workflow_dispatch:
    inputs:
      version_bump:
        description: 'Version bump type'
        required: false
        default: 'patch'
        type: choice
        options: [patch, minor, major]
 
jobs:
  build-and-push:
    uses: ./.github/workflows/_docker-build.yml
    with:
      app_name: '{App Name}'
      image_name: '{registry}/{image-name}'
      dockerfile: 'path/to/Dockerfile'
      context: 'path/to/context'
      version_file: 'path/to/VERSION'
      version_type: 'file'  # or 'package-json'
    secrets: inherit

Step 3: Set Up Version Management

Choose a versioning strategy:

Option A: File-based (Simple)

  • Create VERSION file with semantic version (e.g., 1.0.0)
  • Set version_type: 'file' in workflow

Option B: Package.json-based (Node.js)

  • Use existing package.json version field
  • Set version_type: 'package-json' in workflow

Step 4: Configure Image Factory (Optional)

For advanced lifecycle management:

  1. Add image definition: Update image-factory/images.yaml
  2. Configure source: Link to GitHub workflow
  3. Set rebuild policy: Define dependency tracking and rebuild delays
  4. Deploy Image Factory: Use CDK8s infrastructure definitions

Step 5: Set Up GitOps Integration

For deployment automation:

  1. Kargo Stages: Define promotion stages (dev → staging → prod)
  2. Analysis Templates: Configure image analysis and testing
  3. Freight Management: Automate image promotion between environments

Best Practices

Local Development

  • Local Testing Only: Build local images for testing without pushing to registry
  • Multi-Arch for Registry: Only push multi-architecture builds to GHCR
  • Consistent Tagging: Use git commit hash with -dev suffix for development tags (abc123-dev)
  • Registry Authentication: Keep GHCR authentication current and secure

Security

  • Trivy Scanning: Always include vulnerability scanning
  • SARIF Upload: Integrate security results with GitHub Security tab
  • Registry Security: Use GitHub Container Registry with proper permissions

Versioning

  • Semantic Versioning: Follow semver principles
  • Branch Naming: Use descriptive feature branch names
  • Commit Messages: Include version bump hints in commit messages

Performance

  • Build Caching: Use GitHub Actions cache for Docker layers
  • Multi-stage Builds: Optimize Dockerfile for smaller images
  • Parallel Builds: Leverage multi-architecture builds efficiently

Monitoring

  • Build Notifications: Set up alerts for build failures
  • Registry Monitoring: Track image sizes and vulnerability counts
  • Deployment Tracking: Monitor image promotion through environments

Troubleshooting

Common Issues

Build Failures

  • Check Dockerfile syntax and build context
  • Verify all required files are included in context
  • Review build logs for dependency issues

Version Conflicts

  • Ensure version file format matches version_type
  • Check for concurrent builds causing race conditions
  • Verify git permissions for version commits

Registry Issues

  • Confirm GHCR_TOKEN has proper permissions
  • Check registry quotas and rate limits
  • Verify image name format and registry URL

Debugging Commands

# Local development debugging
docker images | grep ghcr.io
docker buildx ls
docker buildx inspect multiarch
 
# Colima-specific debugging
colima status
colima list
docker context ls
 
# Check workflow status
gh workflow list
gh run list --workflow="{app-name}.yml"
 
# View build logs
gh run view {run-id} --log
 
# Test Docker build locally
docker build -t test-image -f path/to/Dockerfile path/to/context
 
# Test multi-arch build locally
docker buildx build --platform linux/amd64,linux/arm64 -t test-image .
 
# Check image in registry
docker pull ghcr.io/{owner}/{image}:{tag}
docker inspect ghcr.io/{owner}/{image}:{tag}
 
# Scan local image
trivy image ghcr.io/{owner}/{image}:{tag}

Colima-Specific Issues

Colima Not Starting

# Check logs
colima logs
 
# Delete and recreate
colima delete
colima start --cpu 4 --memory 4 --arch aarch64 --vm-type=vz --vz-rosetta

Multi-Arch Build Failures

  • Ensure Rosetta is enabled: --vz-rosetta flag when starting Colima
  • Verify buildx is using the correct builder: docker buildx ls
  • Check available platforms: docker buildx inspect --bootstrap

Docker Context Issues

# List contexts
docker context ls
 
# Switch to Colima context
docker context use colima
 
# Verify
docker ps

Integration with Other Systems

Backstage Integration

  • Images appear in Backstage catalog
  • Build status and metadata visible in developer portal
  • Links to GitHub releases and registry

Kargo Integration

  • Automated promotion pipelines
  • Image analysis and testing stages
  • GitOps-based deployment workflows

Monitoring Integration

  • Build metrics in observability dashboards
  • Security scan results in security tools
  • Deployment tracking across environments

Migration Guide

For Existing Projects

To adopt this workflow in existing projects:

  1. Assessment: Review current build processes and identify applications
  2. Local Setup: Configure Docker Buildx and GHCR authentication
  3. Shared Tasks: Create or update workspace-shared Taskfile with common build tasks
  4. Project Integration: Add project-specific tasks that use shared tasks
  5. GitHub Actions: Create workflows following the template above
  6. Testing: Validate local builds before setting up automated workflows
  7. Migration: Gradually move applications to new workflow
  8. Monitoring: Set up alerts and monitoring for new build process

Development Workflow Migration

Before: Manual docker commands

docker build -t my-app .
docker tag my-app ghcr.io/owner/my-app:latest
docker push ghcr.io/owner/my-app:latest

After: Task-based workflow

# Local testing (no push)
task docker:build:my-app:local
 
# Multi-arch build and push to registry
task docker:build:my-app

This workflow provides a robust, scalable foundation for container lifecycle management that can be adapted to various project needs while maintaining consistency and best practices.