Optimize CI runner image — replace catthehacker/ubuntu:act-latest #120

Closed
opened 2026-07-04 14:44:50 +00:00 by hitanshu · 2 comments
Owner

📋 Overview

Two CFTunnels CI workflows (test_image_build_push.yml and prod_image_tag_promote.yaml) currently use the catthehacker/ubuntu:act-latest Docker image (~1.57 GB) as their container runtime. This investigation evaluates options to reduce disk usage, pull times, and build cache bloat.


🔍 Server Probe Findings (media: 192.168.0.100)

Metric Value
Docker version 28.1.1
Gitea runner gitea_pr_runner (gitea/act_runner:latest)
catthehacker/ubuntu:act-latest 1.57 GB
node:16-bullseye (runner labels) 940 MB (EOL April 2023)
Stale Paketo build cache ~13 GB (20 months old, never pruned)
Runner config file /config/config.yaml = 0 bytes (env-var only)

Runner label mapping (from env vars):

ubuntu-latest:docker://node:16-bullseye
ubuntu-22.04:docker://node:16-bullseye
ubuntu-20.04:docker://node:16-bullseye
ubuntu-18.04:docker://node:16-buster

⚙️ Why the Large Image Exists

The workflows use catthehacker/ubuntu:act-latest because the Gradle + Paketo bootBuildImage step needs:

  • Docker CLI — for docker tag / docker push of the built image to the registry
  • JDK 17 — for the Gradle build and bootBuildImage task
  • Git — for cloning, checkout, and commit metadata in CI

The image is pulled on every job run, sits on disk, but no running container references it — it is purely a CI build-time dependency.


📊 Options Analysis

Create a minimal Alpine-based Dockerfile combining only the essentials.

FROM eclipse-temurin:17-jdk-alpine

RUN apk add --no-cache \
    docker-cli \
    git \
    bash \
    curl

WORKDIR /workspace

Size breakdown:

Layer Size
eclipse-temurin:17-jdk-alpine ~190 MB
docker-cli + git + utils ~40 MB
Total ~230 MB

Savings: ~1.34 GB per pull per disk

Pros:

  • Drastic reduction: 1.57 GB → 230 MB (85% smaller)
  • Alpine base = smaller attack surface, fewer CVEs
  • Self-contained: one image, zero config changes to workflow structure
  • Push to local registry (192.168.0.100:8928/hithomelabs/ci-runner:latest) for fast LAN pulls
  • Can be version-tagged and updated independently

Cons:

  • Requires initial Dockerfile + build to create the image
  • Slight maintenance burden to keep base JDK updated

Changes needed in workflow YAML:

# Before
container:
  image: catthehacker/ubuntu:act-latest

# After
container:
  image: 192.168.0.100:8928/hithomelabs/ci-runner:latest

Option B: Split Build + Dockerize (Two Jobs)

Separate the Gradle build from the containerization step:

  1. build job — uses node:16-bullseye (runner default), runs ./gradlew build, produces JAR artifact
  2. dockerize job — uses docker:cli image (or custom), receives JAR, runs bootBuildImage + push
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: ./gradlew build
      - uses: actions/upload-artifact@v4
        with:
          name: app-jar
          path: build/libs/*.jar

  dockerize:
    needs: build
    runs-on: ubuntu-latest
    container:
      image: docker:cli
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: app-jar
      - run: |
          docker build -t app:latest .
          docker tag app:latest 192.168.0.100:8928/hithomelabs/app:latest
          docker push 192.168.0.100:8928/hithomelabs/app:latest          

Pros:

  • No large image needed; uses runner's default node:16-bullseye for build
  • docker:cli is ~125 MB (but still needs JDK for Paketo bootBuildImage)

Cons:

  • Paketo bootBuildImage still needs JDK — you can't just use docker:cli alone
  • More complex workflow with artifact passing (needs upload/download)
  • Longer overall pipeline due to job orchestration overhead
  • Artifact storage consumption on the runner
  • If the JAR is the only artifact, you'd still need a JDK in the dockerize job if using bootBuildImage (Paketo runs the Gradle plugin which compiles)

Option C: Host Docker Directly (No Container Image)

Remove the container: directive entirely — steps run directly on the host runner.

# Before
container:
  image: catthehacker/ubuntu:act-latest

# After — remove container block, steps run on host

Pros:

  • Zero image overhead — uses whatever the host runner has
  • Simplest YAML change
  • Can access host Docker socket natively

Cons:

  • Loses build isolation — steps share the runner filesystem
  • Runner host needs JDK 17, Docker CLI, Git installed natively
  • Contamination risk between workflow runs
  • Not portable — requires specific host setup
  • Not recommended for multi-tenant or even personal homelab CI where isolation matters

🏆 Recommendation: Option A (Custom Slim Image)

Adopt Option A for the following reasons:

  1. Best size-to-complexity ratio — 85% smaller image with minimal workflow changes
  2. Preserves isolation — each CI run gets a clean container
  3. Self-contained — all dependencies bundled, no host modification
  4. LAN-speed pulls — push to local registry 192.168.0.100:8928/hithomelabs/ci-runner:latest
  5. Easy to maintain — single Dockerfile, can be rebuilt with renovate-style automations

Dockerfile (Draft)

# ci-runner/Dockerfile
FROM eclipse-temurin:17-jdk-alpine

# Install Docker CLI and supporting tools
RUN apk add --no-cache \
    docker-cli \
    docker-compose \
    git \
    bash \
    curl \
    jq \
    && rm -rf /var/cache/apk/*

# Optional: set up a non-root user for safety
RUN addgroup -S ci && adduser -S ci -G ci
USER ci

WORKDIR /workspace

# Metadata for the CI system
LABEL org.opencontainers.image.source="https://gitea.internal/hithomelabs/CI-tools"
LABEL org.opencontainers.image.description="Hithomelabs CI runner image - Temurin 17 JDK + Docker CLI"

Build & Push Command

docker build -t 192.168.0.100:8928/hithomelabs/ci-runner:latest \
  -t 192.168.0.100:8928/hithomelabs/ci-runner:1.0.0 \
  ./ci-runner

docker push 192.168.0.100:8928/hithomelabs/ci-runner:latest
docker push 192.168.0.100:8928/hithomelabs/ci-runner:1.0.0

Workflow Changes

In both test_image_build_push.yml and prod_image_tag_promote.yaml:

# Replace:
container:
  image: catthehacker/ubuntu:act-latest

# With:
container:
  image: 192.168.0.100:8928/hithomelabs/ci-runner:latest

🔄 Secondary Concern: Runner Labels — Upgrade from node:16

The runner label mapping currently uses node:16-bullseye (940 MB, EOL April 2023 — no longer receiving security patches). This is used by all other workflows (not just the two CFTunnels ones).

Recommended change:

# Current (insecure, EOL)
ubuntu-latest:docker://node:16-bullseye
ubuntu-22.04:docker://node:16-bullseye
ubuntu-20.04:docker://node:16-bullseye
ubuntu-18.04:docker://node:16-buster

# Updated
ubuntu-latest:docker://node:20-bookworm
ubuntu-22.04:docker://node:20-bookworm
ubuntu-20.04:docker://node:20-bookworm
ubuntu-18.04:docker://node:18-bullseye

This requires updating the runner's environment variables (or config file if populated) and restarting the gitea_pr_runner container.


🧹 Parallel Concern: Build Cache Pruning

13 GB of stale Paketo buildpack cache found on disk, 20 months old, never pruned.

Recommended action:

# Prune everything (safe since no running builds)
docker buildx prune --all --force

# Or more targeted — remove only build cache
docker builder prune --all --force

# Set up a weekly cron or systemd timer
cat <<'EOF' | sudo tee /etc/cron.weekly/docker-prune
#!/bin/sh
docker image prune --force --filter "until=168h"   # remove images older than 7 days
docker builder prune --all --force                  # purge build cache
docker system prune --force --filter "until=24h"    # clean dangling resources daily-ish
EOF
sudo chmod +x /etc/cron.weekly/docker-prune

This is independent of the image change but highly recommended to reclaim disk space.


📈 Expected Impact

Metric Before After Savings
CI image size 1.57 GB 230 MB 1.34 GB
CI image pull time (1 Gbps LAN) ~12s ~2s ~10s per run
Runner base image (node:16) 940 MB 1.17 GB (node:20) trade-off but secure
Build cache 13 GB 0 GB (pruned) 13 GB reclaimed
Total potential reclaim ~14.3 GB

📝 Implementation Checklist

  • Create ci-runner/Dockerfile with the draft above
  • Build and push to local registry (192.168.0.100:8928/hithomelabs/ci-runner:latest)
  • Update test_image_build_push.yml container image reference
  • Update prod_image_tag_promote.yaml container image reference
  • Run docker buildx prune --all --force on media server
  • Set up weekly Docker cleanup cron (/etc/cron.weekly/docker-prune)
  • Update runner labels: node:16-bullseyenode:20-bookworm
  • Restart gitea_pr_runner container with new label mapping
  • Test-run both CI workflows to verify they pass

/label ~"CI/CD" ~"performance" ~"security"

## 📋 Overview Two CFTunnels CI workflows (`test_image_build_push.yml` and `prod_image_tag_promote.yaml`) currently use the `catthehacker/ubuntu:act-latest` Docker image (~1.57 GB) as their container runtime. This investigation evaluates options to reduce disk usage, pull times, and build cache bloat. --- ## 🔍 Server Probe Findings (media: 192.168.0.100) | Metric | Value | |--------|-------| | Docker version | 28.1.1 | | Gitea runner | `gitea_pr_runner` (gitea/act_runner:latest) | | **catthehacker/ubuntu:act-latest** | **1.57 GB** | | **node:16-bullseye** (runner labels) | **940 MB** (EOL April 2023) | | **Stale Paketo build cache** | **~13 GB** (20 months old, never pruned) | | Runner config file | `/config/config.yaml` = 0 bytes (env-var only) | **Runner label mapping** (from env vars): ``` ubuntu-latest:docker://node:16-bullseye ubuntu-22.04:docker://node:16-bullseye ubuntu-20.04:docker://node:16-bullseye ubuntu-18.04:docker://node:16-buster ``` --- ## ⚙️ Why the Large Image Exists The workflows use `catthehacker/ubuntu:act-latest` because the Gradle + Paketo `bootBuildImage` step needs: - **Docker CLI** — for `docker tag` / `docker push` of the built image to the registry - **JDK 17** — for the Gradle build and `bootBuildImage` task - **Git** — for cloning, checkout, and commit metadata in CI The image is pulled on every job run, sits on disk, but **no running container references it** — it is purely a CI build-time dependency. --- ## 📊 Options Analysis ### Option A: Custom Slim Image (~230 MB) ✅ **RECOMMENDED** Create a minimal Alpine-based Dockerfile combining only the essentials. ```dockerfile FROM eclipse-temurin:17-jdk-alpine RUN apk add --no-cache \ docker-cli \ git \ bash \ curl WORKDIR /workspace ``` **Size breakdown:** | Layer | Size | |-------|------| | `eclipse-temurin:17-jdk-alpine` | ~190 MB | | `docker-cli` + `git` + utils | ~40 MB | | **Total** | **~230 MB** | **Savings: ~1.34 GB per pull per disk** **Pros:** - Drastic reduction: 1.57 GB → 230 MB (**85% smaller**) - Alpine base = smaller attack surface, fewer CVEs - Self-contained: one image, zero config changes to workflow structure - Push to local registry (`192.168.0.100:8928/hithomelabs/ci-runner:latest`) for fast LAN pulls - Can be version-tagged and updated independently **Cons:** - Requires initial Dockerfile + build to create the image - Slight maintenance burden to keep base JDK updated **Changes needed in workflow YAML:** ```yaml # Before container: image: catthehacker/ubuntu:act-latest # After container: image: 192.168.0.100:8928/hithomelabs/ci-runner:latest ``` --- ### Option B: Split Build + Dockerize (Two Jobs) Separate the Gradle build from the containerization step: 1. **`build` job** — uses `node:16-bullseye` (runner default), runs `./gradlew build`, produces JAR artifact 2. **`dockerize` job** — uses `docker:cli` image (or custom), receives JAR, runs `bootBuildImage` + push ```yaml jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: ./gradlew build - uses: actions/upload-artifact@v4 with: name: app-jar path: build/libs/*.jar dockerize: needs: build runs-on: ubuntu-latest container: image: docker:cli steps: - uses: actions/download-artifact@v4 with: name: app-jar - run: | docker build -t app:latest . docker tag app:latest 192.168.0.100:8928/hithomelabs/app:latest docker push 192.168.0.100:8928/hithomelabs/app:latest ``` **Pros:** - No large image needed; uses runner's default `node:16-bullseye` for build - `docker:cli` is ~125 MB (but still needs JDK for Paketo `bootBuildImage`) **Cons:** - **Paketo `bootBuildImage` still needs JDK** — you can't just use `docker:cli` alone - More complex workflow with artifact passing (needs upload/download) - Longer overall pipeline due to job orchestration overhead - Artifact storage consumption on the runner - If the JAR is the only artifact, you'd still need a JDK in the dockerize job if using `bootBuildImage` (Paketo runs the Gradle plugin which compiles) --- ### Option C: Host Docker Directly (No Container Image) Remove the `container:` directive entirely — steps run directly on the host runner. ```yaml # Before container: image: catthehacker/ubuntu:act-latest # After — remove container block, steps run on host ``` **Pros:** - Zero image overhead — uses whatever the host runner has - Simplest YAML change - Can access host Docker socket natively **Cons:** - ❌ **Loses build isolation** — steps share the runner filesystem - ❌ **Runner host needs JDK 17, Docker CLI, Git** installed natively - ❌ **Contamination risk** between workflow runs - ❌ **Not portable** — requires specific host setup - Not recommended for multi-tenant or even personal homelab CI where isolation matters --- ## 🏆 Recommendation: Option A (Custom Slim Image) **Adopt Option A** for the following reasons: 1. **Best size-to-complexity ratio** — 85% smaller image with minimal workflow changes 2. **Preserves isolation** — each CI run gets a clean container 3. **Self-contained** — all dependencies bundled, no host modification 4. **LAN-speed pulls** — push to local registry `192.168.0.100:8928/hithomelabs/ci-runner:latest` 5. **Easy to maintain** — single Dockerfile, can be rebuilt with `renovate`-style automations ### Dockerfile (Draft) ```dockerfile # ci-runner/Dockerfile FROM eclipse-temurin:17-jdk-alpine # Install Docker CLI and supporting tools RUN apk add --no-cache \ docker-cli \ docker-compose \ git \ bash \ curl \ jq \ && rm -rf /var/cache/apk/* # Optional: set up a non-root user for safety RUN addgroup -S ci && adduser -S ci -G ci USER ci WORKDIR /workspace # Metadata for the CI system LABEL org.opencontainers.image.source="https://gitea.internal/hithomelabs/CI-tools" LABEL org.opencontainers.image.description="Hithomelabs CI runner image - Temurin 17 JDK + Docker CLI" ``` ### Build & Push Command ```bash docker build -t 192.168.0.100:8928/hithomelabs/ci-runner:latest \ -t 192.168.0.100:8928/hithomelabs/ci-runner:1.0.0 \ ./ci-runner docker push 192.168.0.100:8928/hithomelabs/ci-runner:latest docker push 192.168.0.100:8928/hithomelabs/ci-runner:1.0.0 ``` ### Workflow Changes In both `test_image_build_push.yml` and `prod_image_tag_promote.yaml`: ```yaml # Replace: container: image: catthehacker/ubuntu:act-latest # With: container: image: 192.168.0.100:8928/hithomelabs/ci-runner:latest ``` --- ## 🔄 Secondary Concern: Runner Labels — Upgrade from node:16 The runner label mapping currently uses **`node:16-bullseye`** (940 MB, EOL April 2023 — no longer receiving security patches). This is used by all other workflows (not just the two CFTunnels ones). **Recommended change:** ``` # Current (insecure, EOL) ubuntu-latest:docker://node:16-bullseye ubuntu-22.04:docker://node:16-bullseye ubuntu-20.04:docker://node:16-bullseye ubuntu-18.04:docker://node:16-buster # Updated ubuntu-latest:docker://node:20-bookworm ubuntu-22.04:docker://node:20-bookworm ubuntu-20.04:docker://node:20-bookworm ubuntu-18.04:docker://node:18-bullseye ``` This requires updating the runner's environment variables (or config file if populated) and restarting the `gitea_pr_runner` container. --- ## 🧹 Parallel Concern: Build Cache Pruning **13 GB of stale Paketo buildpack cache** found on disk, 20 months old, never pruned. **Recommended action:** ```bash # Prune everything (safe since no running builds) docker buildx prune --all --force # Or more targeted — remove only build cache docker builder prune --all --force # Set up a weekly cron or systemd timer cat <<'EOF' | sudo tee /etc/cron.weekly/docker-prune #!/bin/sh docker image prune --force --filter "until=168h" # remove images older than 7 days docker builder prune --all --force # purge build cache docker system prune --force --filter "until=24h" # clean dangling resources daily-ish EOF sudo chmod +x /etc/cron.weekly/docker-prune ``` This is **independent** of the image change but highly recommended to reclaim disk space. --- ## 📈 Expected Impact | Metric | Before | After | Savings | |--------|--------|-------|---------| | CI image size | 1.57 GB | 230 MB | **1.34 GB** | | CI image pull time (1 Gbps LAN) | ~12s | ~2s | **~10s per run** | | Runner base image (`node:16`) | 940 MB | 1.17 GB (node:20) | trade-off but secure | | Build cache | 13 GB | 0 GB (pruned) | **13 GB reclaimed** | | **Total potential reclaim** | **~14.3 GB** | | | --- ## 📝 Implementation Checklist - [ ] Create `ci-runner/Dockerfile` with the draft above - [ ] Build and push to local registry (`192.168.0.100:8928/hithomelabs/ci-runner:latest`) - [ ] Update `test_image_build_push.yml` container image reference - [ ] Update `prod_image_tag_promote.yaml` container image reference - [ ] Run `docker buildx prune --all --force` on media server - [ ] Set up weekly Docker cleanup cron (`/etc/cron.weekly/docker-prune`) - [ ] Update runner labels: `node:16-bullseye` → `node:20-bookworm` - [ ] Restart `gitea_pr_runner` container with new label mapping - [ ] Test-run both CI workflows to verify they pass --- /label ~"CI/CD" ~"performance" ~"security"
hitanshu added the
architect:complete
CI/CD
docker
performance
security
labels 2026-07-04 14:45:01 +00:00
Author
Owner

🧪 Test Plan: Validating the Custom CI Runner Image Before Promotion

Per the user's request, here is the test plan to validate the custom slim image without touching CFTunnels workflows until we're confident.

Strategy: Isolate → Validate → Promote

We've created an epic in Hithomelabs/HomeLabDocker (#58) with 6 user stories that implement this plan:

Step Issue Description
1️⃣ #59 Build & Push the custom image (eclipse-temurin:17-jdk-alpine + docker-cli + git) to 192.168.0.100:8928/hithomelabs/ci-runner:1.0.0
2️⃣ #60 Create test repo hitanshu/ci-runner-test with a minimal Gradle/Spring Boot project + Gitea Actions workflow
3️⃣ #61 Run the test workflow manually (workflow_dispatch), validate JDK 17, Docker CLI, Git, bootBuildImage, and docker push all work
4️⃣ #62 Promote to CFTunnels — update test_image_build_push.yml and prod_image_tag_promote.yaml via PR with pinned version tag
5️⃣ #63 Update runner labels — replace EOL node:16-bullseyenode:20-bookworm
6️⃣ #64 Prune 13 GB stale build cache + set up weekly cleanup cron

Key Design Decisions

  • Test repo is personal (hitanshu/ci-runner-test), not org — no risk of affecting production workflows
  • Workflow uses workflow_dispatch only — never triggers on push
  • Image is version-pinned (:1.0.0 not :latest) in CFTunnels for deterministic builds
  • PR-based promotion — feature branch → PR → CI check → merge (with rollback by reverting)
  • Build cache is a separate issue — a lighter image alone won't fix the 13 GB stale cache

Rollback Plan

If prod CI breaks after promotion:

  1. Revert the commit that changed the container: image
  2. The old catthehacker/ubuntu:act-latest is still on disk
  3. Re-merge the revert PR

Full Plan

See the detailed epic: Hithomelabs/HomeLabDocker#58Hithomelabs/HomeLabDocker#58

## 🧪 Test Plan: Validating the Custom CI Runner Image Before Promotion Per the user's request, here is the test plan to validate the custom slim image **without touching CFTunnels workflows** until we're confident. ### Strategy: Isolate → Validate → Promote We've created an **epic** in `Hithomelabs/HomeLabDocker` (#58) with 6 user stories that implement this plan: | Step | Issue | Description | |------|-------|-------------| | 1️⃣ | #59 | **Build & Push** the custom image (`eclipse-temurin:17-jdk-alpine` + docker-cli + git) to `192.168.0.100:8928/hithomelabs/ci-runner:1.0.0` | | 2️⃣ | #60 | **Create test repo** `hitanshu/ci-runner-test` with a minimal Gradle/Spring Boot project + Gitea Actions workflow | | 3️⃣ | #61 | **Run the test workflow** manually (`workflow_dispatch`), validate JDK 17, Docker CLI, Git, `bootBuildImage`, and `docker push` all work | | 4️⃣ | #62 | **Promote to CFTunnels** — update `test_image_build_push.yml` and `prod_image_tag_promote.yaml` via PR with pinned version tag | | 5️⃣ | #63 | **Update runner labels** — replace EOL `node:16-bullseye` → `node:20-bookworm` | | 6️⃣ | #64 | **Prune 13 GB stale build cache** + set up weekly cleanup cron | ### Key Design Decisions - **Test repo is personal** (`hitanshu/ci-runner-test`), not org — no risk of affecting production workflows - **Workflow uses `workflow_dispatch`** only — never triggers on push - **Image is version-pinned** (`:1.0.0` not `:latest`) in CFTunnels for deterministic builds - **PR-based promotion** — feature branch → PR → CI check → merge (with rollback by reverting) - **Build cache is a separate issue** — a lighter image alone won't fix the 13 GB stale cache ### Rollback Plan If prod CI breaks after promotion: 1. Revert the commit that changed the `container:` image 2. The old `catthehacker/ubuntu:act-latest` is still on disk 3. Re-merge the revert PR ### Full Plan See the detailed epic: **Hithomelabs/HomeLabDocker#58** → https://gitea.hithomelabs.com/Hithomelabs/HomeLabDocker/issues/58
Member

══════════════════════════════════════════════════════════
PM AGENT COMPLETED ✓
══════════════════════════════════════════════════════════

Milestone: CI Runner Optimization — Custom Slim Image & Test Validation — #3
Epic: Hithomelabs/HomeLabDocker#65

Created 6 user stories under Polly in Hithomelabs/HomeLabDocker:

# Title Pts Priority
#66 Build & Push Custom CI Runner Image to Local Registry 5 high
#67 Create Test Repo & Validation Workflow for CI Runner 5 high
#68 Run Test Workflow & Validate Custom CI Runner Image 3 high
#69 Promote Custom CI Runner Image to CFTunnels Workflows 3 medium
#70 Update Gitea Runner Labels — Replace EOL node:16 with node:22 3 medium
#71 Prune Stale Docker Build Cache (13 GB Paketo Cache) 1 low

Total Story Points: 20
Priority Distribution: critical[0] high[3] medium[2] low[1]

Critical Path:
#66 (Build & Push) → #67 (Create Test Repo) → #68 (Validate) → #69 (Promote)

Independent (can run in parallel):
#70 (Runner Labels) and #71 (Prune Cache) are independent of the critical path

ADR / Architect Analysis: Documented in this issue (CFTunnels#120) by hitanshu

Closing this issue — all implementation work tracked in HomeLabDocker epic #65 and its child stories.
══════════════════════════════════════════════════════════

══════════════════════════════════════════════════════════ PM AGENT COMPLETED ✓ ══════════════════════════════════════════════════════════ **Milestone:** CI Runner Optimization — Custom Slim Image & Test Validation — #3 **Epic:** Hithomelabs/HomeLabDocker#65 Created **6 user stories** under **Polly** in `Hithomelabs/HomeLabDocker`: | # | Title | Pts | Priority | |---|-------|-----|----------| | #66 | Build & Push Custom CI Runner Image to Local Registry | 5 | high | | #67 | Create Test Repo & Validation Workflow for CI Runner | 5 | high | | #68 | Run Test Workflow & Validate Custom CI Runner Image | 3 | high | | #69 | Promote Custom CI Runner Image to CFTunnels Workflows | 3 | medium | | #70 | Update Gitea Runner Labels — Replace EOL node:16 with node:22 | 3 | medium | | #71 | Prune Stale Docker Build Cache (13 GB Paketo Cache) | 1 | low | **Total Story Points:** 20 **Priority Distribution:** critical[0] high[3] medium[2] low[1] **Critical Path:** #66 (Build & Push) → #67 (Create Test Repo) → #68 (Validate) → #69 (Promote) **Independent (can run in parallel):** #70 (Runner Labels) and #71 (Prune Cache) are independent of the critical path **ADR / Architect Analysis:** Documented in this issue (CFTunnels#120) by hitanshu ✅ Closing this issue — all implementation work tracked in HomeLabDocker epic #65 and its child stories. ══════════════════════════════════════════════════════════
Polly closed this issue 2026-07-04 17:41:03 +00:00
Sign in to join this conversation.
No Milestone
No project
No Assignees
2 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: Hithomelabs/CFTunnels#120
No description provided.