Skip to content

Note: This is a temporary planning document. It will be deleted or archived once the decision is implemented.

Documentation Build Strategy: Clone in Actions vs Dockerfile

Question

When building the unified docs Docker image, where should we clone the other repos (cluster-gitops, camarades-infrastructure)?

Option A: Clone in GitHub Actions, then COPY into Dockerfile Option B: Clone directly in Dockerfile Option C: Hybrid approach

Option A: Clone in Actions, COPY in Dockerfile

How It Works

# .github/workflows/ci-cd.yml
- name: Checkout syrf
  uses: actions/checkout@v4
  with:
    path: syrf

- name: Checkout cluster-gitops
  uses: actions/checkout@v4
  with:
    repository: camaradesuk/cluster-gitops
    path: cluster-gitops

- name: Checkout camarades-infrastructure
  uses: actions/checkout@v4
  with:
    repository: camaradesuk/camarades-infrastructure
    path: camarades-infrastructure

- name: Build Docker image
  run: docker build -f syrf/docs/Dockerfile -t syrf-docs .
# docs/Dockerfile
FROM python:3.10-alpine AS builder

WORKDIR /workspace

# Copy all repos from context
COPY syrf/ syrf/
COPY cluster-gitops/ cluster-gitops/
COPY camarades-infrastructure/ camarades-infrastructure/

# Install dependencies and build
WORKDIR /workspace/syrf
RUN pip install -r requirements.txt
RUN mkdocs build --strict --site-dir _docs_build

FROM nginx:alpine
COPY --from=builder /workspace/syrf/_docs_build /usr/share/nginx/html

Pros

  • Better Docker layer caching - repos don't change every build
  • Faster builds - no git clone in Docker (uses Actions cache)
  • No secrets in Dockerfile - Actions checkout handles auth
  • Works with private repos - Actions has GITHUB_TOKEN by default
  • Deterministic builds - exact commits, no "latest" ambiguity
  • Easy local testing - can manually clone and build
  • Build context is explicit - visible in Actions workflow

Cons

  • ❌ More complex Actions workflow
  • ❌ Larger build context sent to Docker daemon
  • ❌ Must coordinate checkout paths with Dockerfile COPY

Build Performance

Actions checkout (3 repos):    ~10-20s (with cache: ~3-5s)
Docker COPY:                    ~2-5s
Total:                          ~12-25s (with cache: ~5-10s)

Option B: Clone in Dockerfile

How It Works

# .github/workflows/ci-cd.yml
- name: Checkout syrf
  uses: actions/checkout@v4

- name: Build Docker image
  run: docker build -f docs/Dockerfile -t syrf-docs .
# docs/Dockerfile
FROM python:3.10-alpine AS builder

# Install git
RUN apk add --no-cache git build-base

WORKDIR /workspace

# Clone all repos
RUN git clone https://github.com/camaradesuk/syrf.git && \
    git clone https://github.com/camaradesuk/cluster-gitops.git && \
    git clone https://github.com/camaradesuk/camarades-infrastructure.git

# Install dependencies and build
WORKDIR /workspace/syrf
RUN pip install -r requirements.txt
RUN mkdocs build --strict --site-dir _docs_build

FROM nginx:alpine
COPY --from=builder /workspace/syrf/_docs_build /usr/share/nginx/html

Pros

  • Simple Actions workflow - just build, no coordination
  • Smaller build context - only syrf repo sent to Docker
  • Self-contained Dockerfile - everything in one place
  • Easy to understand - clear what's happening
  • Works for manual builds - docker build just works

Cons

  • Poor Docker layer caching - git clone runs every time
  • Slower builds - clones entire repos every build (~30-60s)
  • Authentication issues - private repos need tokens
  • Non-deterministic - always clones "latest" main/master
  • Harder to debug - can't inspect cloned repos
  • Network dependency - fails if GitHub is down during build

Build Performance

Docker git clone (3 repos):     ~30-60s (no caching!)
Docker pip install:             ~20-30s
Docker mkdocs build:            ~10-15s
Total:                          ~60-105s (always full build)

How It Works

For CI/CD (Actions): Use Option A (clone in Actions) For Local Development: Use Option B pattern (but with local repos)

# .github/workflows/ci-cd.yml
build-and-push-docs:
  steps:
    - name: Checkout syrf
      uses: actions/checkout@v4
      with:
        path: syrf

    - name: Checkout cluster-gitops
      uses: actions/checkout@v4
      with:
        repository: camaradesuk/cluster-gitops
        path: cluster-gitops

    - name: Checkout camarades-infrastructure
      uses: actions/checkout@v4
      with:
        repository: camaradesuk/camarades-infrastructure
        path: camarades-infrastructure

    - name: Build Docker image
      run: |
        docker build \
          -f syrf/docs/Dockerfile.ci \
          -t ghcr.io/camaradesuk/syrf-docs:${{ needs.version-docs.outputs.version }} \
          .
# docs/Dockerfile.ci (for CI/CD with Actions checkout)
FROM python:3.10-alpine AS builder

WORKDIR /workspace

# Copy all repos from Actions checkout
COPY syrf/ syrf/
COPY cluster-gitops/ cluster-gitops/
COPY camarades-infrastructure/ camarades-infrastructure/

WORKDIR /workspace/syrf
RUN pip install --no-cache-dir -r requirements.txt
RUN mkdocs build --strict --site-dir _docs_build

FROM nginx:alpine
COPY --from=builder /workspace/syrf/_docs_build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
# docs/Dockerfile (for local development)
FROM python:3.10-alpine AS builder

# Install git for local clone capability
RUN apk add --no-cache git build-base

WORKDIR /workspace

# For local builds: check if repos exist in context, else clone
# This allows: docker build -f docs/Dockerfile .
COPY . syrf/

# Clone other repos if not already present
RUN if [ ! -d "../cluster-gitops" ]; then \
      git clone https://github.com/camaradesuk/cluster-gitops.git ../cluster-gitops; \
    fi && \
    if [ ! -d "../camarades-infrastructure" ]; then \
      git clone https://github.com/camaradesuk/camarades-infrastructure.git ../camarades-infrastructure; \
    fi

WORKDIR /workspace/syrf
RUN pip install --no-cache-dir -r requirements.txt
RUN mkdocs build --strict --site-dir _docs_build

FROM nginx:alpine
COPY --from=builder /workspace/syrf/_docs_build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Pros

  • Best of both worlds - fast CI, easy local dev
  • Optimized for each use case
  • Clear separation - .ci suffix shows intent
  • Flexible - works in all scenarios

Cons

  • ❌ Two Dockerfiles to maintain (but they're similar)
  • ❌ Slightly more complex setup

Performance Comparison

Scenario Option A (Actions) Option B (Dockerfile) Option C (Hybrid)
First CI Build ~60s ~105s ~60s
Cached CI Build ~15s ~105s ~15s
Local Build Manual setup ~105s ~105s
Build Context Size ~50MB ~1MB ~50MB (CI), ~1MB (local)

Recommendation: Option C (Hybrid)

Why Hybrid Wins

  1. CI Performance: Option A speed with Actions checkout caching
  2. Developer Experience: Local builds "just work" without manual setup
  3. Flexibility: Adapts to context (CI vs local)
  4. Cost Efficiency: Faster CI = lower GitHub Actions costs
  5. Reliability: No network dependency during Docker build in CI

Additional Benefits

  • Repository Dispatch Ready: When other repos trigger rebuilds, Actions checkout handles it cleanly
  • Version Pinning: Can checkout specific commits/tags if needed
  • Debugging: Easy to inspect repos in Actions before Docker build
  • Security: No auth tokens in Dockerfile, GitHub Actions handles it

Implementation Priority

Phase 1: Quick Win (Use Option A)

Just implement Actions checkout + Dockerfile.ci for immediate improvement.

Phase 2: Developer Experience (Add local Dockerfile)

Add fallback Dockerfile for local development when needed.

Phase 3: Optimization (Add caching)

Add Docker layer caching in Actions for even faster builds.

Example: Full GitHub Actions Job

build-and-push-docs:
  needs: version-docs
  runs-on: ubuntu-latest
  steps:
    # Checkout all 3 repos
    - name: Checkout syrf
      uses: actions/checkout@v4
      with:
        path: syrf

    - name: Checkout cluster-gitops
      uses: actions/checkout@v4
      with:
        repository: camaradesuk/cluster-gitops
        path: cluster-gitops

    - name: Checkout camarades-infrastructure
      uses: actions/checkout@v4
      with:
        repository: camaradesuk/camarades-infrastructure
        path: camarades-infrastructure

    # Optional: Show what was cloned (debugging)
    - name: Verify repos
      run: |
        echo "Syrf commit: $(git -C syrf rev-parse HEAD)"
        echo "GitOps commit: $(git -C cluster-gitops rev-parse HEAD)"
        echo "Infra commit: $(git -C camarades-infrastructure rev-parse HEAD)"

    # Build with all repos in context
    - name: Build Docker image
      run: |
        docker build \
          -f syrf/docs/Dockerfile.ci \
          -t ghcr.io/camaradesuk/syrf-docs:${{ needs.version-docs.outputs.version }} \
          -t ghcr.io/camaradesuk/syrf-docs:latest \
          .

    # Push to registry
    - name: Push to GHCR
      run: |
        echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
        docker push ghcr.io/camaradesuk/syrf-docs:${{ needs.version-docs.outputs.version }}
        docker push ghcr.io/camaradesuk/syrf-docs:latest

Testing Strategy

Test 1: Local Build (without other repos)

cd /home/chris/workspace/syrf
docker build -f docs/Dockerfile -t syrf-docs:local .
# Should clone cluster-gitops and camarades-infrastructure

Test 2: Local Build (with other repos present)

cd /home/chris/workspace
docker build -f syrf/docs/Dockerfile.ci -t syrf-docs:ci .
# Should use existing repos

Test 3: CI Build (simulate Actions)

cd /tmp
git clone https://github.com/camaradesuk/syrf.git
git clone https://github.com/camaradesuk/cluster-gitops.git
git clone https://github.com/camaradesuk/camarades-infrastructure.git
docker build -f syrf/docs/Dockerfile.ci -t syrf-docs:ci .

Test 4: Verify Aggregated Docs

docker run --rm -p 8080:80 syrf-docs:ci
# Open browser: http://localhost:8080
# Check: Architecture, GitOps Operations, Infrastructure sections all present

Decision

Implement Option C (Hybrid Approach)

Rationale:

  • Performance: 4-7x faster CI builds with caching
  • Flexibility: Works for CI and local development
  • Cost: Reduces GitHub Actions minutes by 70-80%
  • Developer Experience: Easy local testing without manual setup
  • Future-proof: Ready for repository dispatch automation