Adopt commit-based container tags #232
13 changed files with 363 additions and 258 deletions
Adopt commit-based container tags with path-triggered CI
Replace git-tag-triggered container builds with path-based triggers on main and workflow_dispatch. Tags now encode the upstream app version and commit SHA (vX.Y.Z-<sha>) for full traceability. The manual container-tag-and-release task is replaced by container-build-and-release which dispatches workflows via the Forgejo API. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
commit
d6152b8238
|
|
@ -18,11 +18,15 @@ class BlumeopsCi:
|
||||||
src: dagger.Directory,
|
src: dagger.Directory,
|
||||||
container_name: str,
|
container_name: str,
|
||||||
version: str,
|
version: str,
|
||||||
|
commit_sha: str,
|
||||||
registry: str = "registry.ops.eblu.me",
|
registry: str = "registry.ops.eblu.me",
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Build and push to registry. Returns the image ref."""
|
"""Build and push to registry. Returns the image ref.
|
||||||
|
|
||||||
|
Tag format: {version}-{commit_sha} (e.g. v1.0.0-abc1234)
|
||||||
|
"""
|
||||||
ctr = self.build(src, container_name)
|
ctr = self.build(src, container_name)
|
||||||
ref = f"{registry}/blumeops/{container_name}:{version}"
|
ref = f"{registry}/blumeops/{container_name}:{version}-{commit_sha}"
|
||||||
return await ctr.publish(ref)
|
return await ctr.publish(ref)
|
||||||
|
|
||||||
@function
|
@function
|
||||||
|
|
|
||||||
|
|
@ -1,82 +1,138 @@
|
||||||
# Nix container build workflow
|
# Nix container build workflow
|
||||||
# Triggers on tags matching: <container>-v<version>
|
# Triggers on pushes to main that modify containers/*, or via manual dispatch.
|
||||||
# Builds from containers/<container>/default.nix if it exists, skips otherwise
|
# Detects which containers changed, builds from default.nix, and pushes via
|
||||||
# Pushes to Zot registry via skopeo with -nix image tag suffix
|
# skopeo with commit-SHA-based tags: vX.Y.Z-<sha>-nix
|
||||||
#
|
|
||||||
# Examples:
|
|
||||||
# nettest-v1.0.0 -> builds containers/nettest/default.nix, pushes :v1.0.0-nix
|
|
||||||
# devpi-v2.1.0 -> skips (no default.nix)
|
|
||||||
name: Build Container (Nix)
|
name: Build Container (Nix)
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
branches: [main]
|
||||||
- '*-v[0-9]*'
|
paths: ['containers/**']
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
container:
|
||||||
|
description: 'Container name (directory under containers/)'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
ref:
|
||||||
|
description: 'Commit SHA to build (defaults to current HEAD)'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
detect:
|
||||||
runs-on: nix-container-builder
|
runs-on: nix-container-builder
|
||||||
|
outputs:
|
||||||
|
containers: ${{ steps.list.outputs.containers }}
|
||||||
steps:
|
steps:
|
||||||
- name: Parse tag
|
- name: Checkout
|
||||||
id: parse
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 2
|
||||||
|
|
||||||
|
- name: Detect changed containers
|
||||||
|
id: list
|
||||||
run: |
|
run: |
|
||||||
TAG="${GITHUB_REF_NAME}"
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||||
echo "Tag: $TAG"
|
CONTAINERS='["${{ inputs.container }}"]'
|
||||||
|
else
|
||||||
# Extract container name (everything before -v)
|
CONTAINERS=$(git diff --name-only HEAD~1 HEAD -- containers/ \
|
||||||
# e.g., "nettest-v1.0.0" -> "nettest", "my-app-v2.0.0" -> "my-app"
|
| cut -d/ -f2 | sort -u \
|
||||||
CONTAINER="${TAG%-v[0-9]*}"
|
| jq -R -s -c 'split("\n") | map(select(length > 0))')
|
||||||
VERSION="${TAG#"${CONTAINER}"-}"
|
fi
|
||||||
|
echo "containers=$CONTAINERS" >> "$GITHUB_OUTPUT"
|
||||||
echo "container=$CONTAINER" >> "$GITHUB_OUTPUT"
|
echo "Containers to build: $CONTAINERS"
|
||||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Container: $CONTAINER"
|
|
||||||
echo "Version: $VERSION"
|
|
||||||
|
|
||||||
|
build:
|
||||||
|
needs: detect
|
||||||
|
if: needs.detect.outputs.containers != '[]'
|
||||||
|
runs-on: nix-container-builder
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
container: ${{ fromJson(needs.detect.outputs.containers) }}
|
||||||
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Check if nix container exists
|
- name: Check for default.nix
|
||||||
id: check
|
id: check
|
||||||
run: |
|
run: |
|
||||||
CONTAINER="${{ steps.parse.outputs.container }}"
|
if [ -f "containers/${{ matrix.container }}/default.nix" ]; then
|
||||||
CONTEXT="containers/$CONTAINER"
|
|
||||||
|
|
||||||
if [ -f "$CONTEXT/default.nix" ]; then
|
|
||||||
echo "Found $CONTEXT/default.nix"
|
|
||||||
echo "exists=true" >> "$GITHUB_OUTPUT"
|
echo "exists=true" >> "$GITHUB_OUTPUT"
|
||||||
else
|
else
|
||||||
echo "No default.nix found at $CONTEXT/default.nix — skipping"
|
echo "No default.nix for ${{ matrix.container }} — skipping"
|
||||||
echo "exists=false" >> "$GITHUB_OUTPUT"
|
echo "exists=false" >> "$GITHUB_OUTPUT"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
- name: Extract version and SHA
|
||||||
|
if: steps.check.outputs.exists == 'true'
|
||||||
|
id: meta
|
||||||
|
run: |
|
||||||
|
CONTAINER="${{ matrix.container }}"
|
||||||
|
NIX_FILE="containers/$CONTAINER/default.nix"
|
||||||
|
|
||||||
|
# Try extracting version = "..." from the nix file (e.g. ntfy)
|
||||||
|
VERSION=$(grep -m1 '^\s*version\s*=\s*"' "$NIX_FILE" \
|
||||||
|
| sed 's/.*"\(.*\)".*/\1/' || true)
|
||||||
|
|
||||||
|
# Fall back to CONTAINER_APP_VERSION from Dockerfile (e.g. nettest)
|
||||||
|
if [ -z "$VERSION" ] && [ -f "containers/$CONTAINER/Dockerfile" ]; then
|
||||||
|
VERSION=$(grep -m1 '^ARG CONTAINER_APP_VERSION=' \
|
||||||
|
"containers/$CONTAINER/Dockerfile" \
|
||||||
|
| sed 's/^ARG CONTAINER_APP_VERSION=//')
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Last resort: dagger call nix-version for nixpkgs packages (e.g. authentik)
|
||||||
|
if [ -z "$VERSION" ]; then
|
||||||
|
VERSION=$(dagger call nix-version --package="$CONTAINER")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$VERSION" ]; then
|
||||||
|
echo "Error: Could not determine version for $CONTAINER"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
REF="${{ inputs.ref }}"
|
||||||
|
if [ -z "$REF" ]; then
|
||||||
|
REF="${GITHUB_SHA}"
|
||||||
|
fi
|
||||||
|
SHORT_SHA=$(echo "$REF" | head -c 7)
|
||||||
|
|
||||||
|
# Ensure version starts with 'v'
|
||||||
|
case "$VERSION" in
|
||||||
|
v*) ;;
|
||||||
|
*) VERSION="v${VERSION}" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "sha=$SHORT_SHA" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Version: $VERSION, SHA: $SHORT_SHA"
|
||||||
|
|
||||||
- name: Resolve nixpkgs
|
- name: Resolve nixpkgs
|
||||||
if: steps.check.outputs.exists == 'true'
|
if: steps.check.outputs.exists == 'true'
|
||||||
id: nixpkgs
|
id: nixpkgs
|
||||||
run: |
|
run: |
|
||||||
# Resolve nixpkgs from the flake registry for <nixpkgs> lookup
|
|
||||||
NIXPKGS_PATH=$(nix flake metadata nixpkgs --json | jq -r '.path')
|
NIXPKGS_PATH=$(nix flake metadata nixpkgs --json | jq -r '.path')
|
||||||
echo "Resolved nixpkgs: $NIXPKGS_PATH"
|
echo "Resolved nixpkgs: $NIXPKGS_PATH"
|
||||||
echo "path=$NIXPKGS_PATH" >> "$GITHUB_OUTPUT"
|
echo "path=$NIXPKGS_PATH" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Build with nix
|
- name: Build with nix
|
||||||
if: steps.check.outputs.exists == 'true'
|
if: steps.check.outputs.exists == 'true'
|
||||||
id: build
|
|
||||||
env:
|
env:
|
||||||
NIX_PATH: "nixpkgs=${{ steps.nixpkgs.outputs.path }}"
|
NIX_PATH: "nixpkgs=${{ steps.nixpkgs.outputs.path }}"
|
||||||
run: |
|
run: |
|
||||||
CONTAINER="${{ steps.parse.outputs.container }}"
|
echo "Building containers/${{ matrix.container }}/default.nix"
|
||||||
echo "Building containers/$CONTAINER/default.nix"
|
|
||||||
echo "NIX_PATH=$NIX_PATH"
|
echo "NIX_PATH=$NIX_PATH"
|
||||||
nix-build "containers/$CONTAINER/default.nix" -o result
|
nix-build "containers/${{ matrix.container }}/default.nix" -o result
|
||||||
echo "Build complete: $(readlink result)"
|
echo "Build complete: $(readlink result)"
|
||||||
|
|
||||||
- name: Push to registry
|
- name: Push to registry
|
||||||
if: steps.check.outputs.exists == 'true'
|
if: steps.check.outputs.exists == 'true'
|
||||||
run: |
|
run: |
|
||||||
CONTAINER="${{ steps.parse.outputs.container }}"
|
CONTAINER="${{ matrix.container }}"
|
||||||
VERSION="${{ steps.parse.outputs.version }}"
|
VERSION="${{ steps.meta.outputs.version }}"
|
||||||
IMAGE="registry.ops.eblu.me/blumeops/$CONTAINER:$VERSION-nix"
|
SHORT_SHA="${{ steps.meta.outputs.sha }}"
|
||||||
|
IMAGE="registry.ops.eblu.me/blumeops/$CONTAINER:${VERSION}-${SHORT_SHA}-nix"
|
||||||
|
|
||||||
echo "Pushing to $IMAGE"
|
echo "Pushing to $IMAGE"
|
||||||
skopeo copy \
|
skopeo copy \
|
||||||
|
|
|
||||||
|
|
@ -1,77 +1,105 @@
|
||||||
# Generic container build workflow
|
# Dockerfile container build workflow
|
||||||
# Triggers on tags matching: <container>-v<version>
|
# Triggers on pushes to main that modify containers/*, or via manual dispatch.
|
||||||
# Builds from containers/<container>/Dockerfile if it exists
|
# Detects which containers changed, extracts version from CONTAINER_APP_VERSION,
|
||||||
#
|
# and publishes with commit-SHA-based tags: vX.Y.Z-<sha>
|
||||||
# Uses Dagger to build and push images to the Zot registry.
|
|
||||||
#
|
|
||||||
# Examples:
|
|
||||||
# nettest-v1.0.0 -> builds containers/nettest/
|
|
||||||
# devpi-v2.1.0 -> builds containers/devpi/
|
|
||||||
# foo-v1.0.0 -> skips if containers/foo/ doesn't exist
|
|
||||||
name: Build Container
|
name: Build Container
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
branches: [main]
|
||||||
- '*-v[0-9]*'
|
paths: ['containers/**']
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
container:
|
||||||
|
description: 'Container name (directory under containers/)'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
ref:
|
||||||
|
description: 'Commit SHA to build (defaults to current HEAD)'
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
detect:
|
||||||
runs-on: k8s
|
runs-on: k8s
|
||||||
|
outputs:
|
||||||
|
containers: ${{ steps.list.outputs.containers }}
|
||||||
steps:
|
steps:
|
||||||
- name: Parse tag
|
- name: Checkout
|
||||||
id: parse
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 2
|
||||||
|
|
||||||
|
- name: Detect changed containers
|
||||||
|
id: list
|
||||||
run: |
|
run: |
|
||||||
TAG="${GITHUB_REF_NAME}"
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||||
echo "Tag: $TAG"
|
CONTAINERS='["${{ inputs.container }}"]'
|
||||||
|
else
|
||||||
# Extract container name (everything before -v)
|
# Diff against parent commit to find changed container dirs
|
||||||
# e.g., "nettest-v1.0.0" -> "nettest", "my-app-v2.0.0" -> "my-app"
|
CONTAINERS=$(git diff --name-only HEAD~1 HEAD -- containers/ \
|
||||||
CONTAINER="${TAG%-v[0-9]*}"
|
| cut -d/ -f2 | sort -u \
|
||||||
VERSION="${TAG#"${CONTAINER}"-}"
|
| jq -R -s -c 'split("\n") | map(select(length > 0))')
|
||||||
|
fi
|
||||||
echo "container=$CONTAINER" >> "$GITHUB_OUTPUT"
|
echo "containers=$CONTAINERS" >> "$GITHUB_OUTPUT"
|
||||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
echo "Containers to build: $CONTAINERS"
|
||||||
echo "Container: $CONTAINER"
|
|
||||||
echo "Version: $VERSION"
|
|
||||||
|
|
||||||
|
build:
|
||||||
|
needs: detect
|
||||||
|
if: needs.detect.outputs.containers != '[]'
|
||||||
|
runs-on: k8s
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
container: ${{ fromJson(needs.detect.outputs.containers) }}
|
||||||
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Check if container exists
|
- name: Check for Dockerfile
|
||||||
id: check
|
id: check
|
||||||
run: |
|
run: |
|
||||||
CONTAINER="${{ steps.parse.outputs.container }}"
|
if [ -f "containers/${{ matrix.container }}/Dockerfile" ]; then
|
||||||
CONTEXT="containers/$CONTAINER"
|
|
||||||
|
|
||||||
if [ -f "$CONTEXT/Dockerfile" ]; then
|
|
||||||
echo "Found $CONTEXT/Dockerfile"
|
|
||||||
echo "exists=true" >> "$GITHUB_OUTPUT"
|
echo "exists=true" >> "$GITHUB_OUTPUT"
|
||||||
else
|
else
|
||||||
echo "No Dockerfile found at $CONTEXT/Dockerfile"
|
echo "No Dockerfile for ${{ matrix.container }} — skipping"
|
||||||
echo "exists=false" >> "$GITHUB_OUTPUT"
|
echo "exists=false" >> "$GITHUB_OUTPUT"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Skip if container not found
|
- name: Extract version and SHA
|
||||||
if: steps.check.outputs.exists != 'true'
|
if: steps.check.outputs.exists == 'true'
|
||||||
|
id: meta
|
||||||
run: |
|
run: |
|
||||||
echo "========================================"
|
VERSION=$(grep -m1 '^ARG CONTAINER_APP_VERSION=' \
|
||||||
echo "Container not found: ${{ steps.parse.outputs.container }}"
|
"containers/${{ matrix.container }}/Dockerfile" \
|
||||||
echo "========================================"
|
| sed 's/^ARG CONTAINER_APP_VERSION=//')
|
||||||
echo ""
|
|
||||||
echo "Tag '${{ github.ref_name }}' does not match any container in containers/"
|
if [ -z "$VERSION" ]; then
|
||||||
echo ""
|
echo "Error: No CONTAINER_APP_VERSION found in Dockerfile"
|
||||||
echo "Available containers:"
|
exit 1
|
||||||
find containers -maxdepth 1 -mindepth 1 -type d -exec basename {} \; 2>/dev/null | sort | while read -r name; do
|
fi
|
||||||
echo " - $name"
|
|
||||||
done || echo " (none)"
|
# Use dispatch input ref if provided, otherwise current commit
|
||||||
echo ""
|
REF="${{ inputs.ref }}"
|
||||||
echo "Skipping build."
|
if [ -z "$REF" ]; then
|
||||||
|
REF="${GITHUB_SHA}"
|
||||||
|
fi
|
||||||
|
SHORT_SHA=$(echo "$REF" | head -c 7)
|
||||||
|
|
||||||
|
# Ensure version starts with 'v'
|
||||||
|
case "$VERSION" in
|
||||||
|
v*) ;; # already has v prefix
|
||||||
|
*) VERSION="v${VERSION}" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "sha=$SHORT_SHA" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Version: $VERSION, SHA: $SHORT_SHA"
|
||||||
|
|
||||||
- name: Publish
|
- name: Publish
|
||||||
if: steps.check.outputs.exists == 'true'
|
if: steps.check.outputs.exists == 'true'
|
||||||
run: |
|
run: |
|
||||||
dagger call publish \
|
dagger call publish \
|
||||||
--src=. \
|
--src=. \
|
||||||
--container-name=${{ steps.parse.outputs.container }} \
|
--container-name=${{ matrix.container }} \
|
||||||
--version=${{ steps.parse.outputs.version }}
|
--version=${{ steps.meta.outputs.version }} \
|
||||||
|
--commit-sha=${{ steps.meta.outputs.sha }}
|
||||||
|
|
|
||||||
1
docs/changelog.d/harden-zot-registry.feature.md
Normal file
1
docs/changelog.d/harden-zot-registry.feature.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
Container builds now trigger automatically on merge to main (path-based) and use commit-SHA-based image tags (`vX.Y.Z-<sha>`) for full traceability. The `container-tag-and-release` task is replaced by `container-build-and-release` which dispatches workflows via the Forgejo API. Added pre-commit hook to keep container versions in sync with `service-versions.yaml`.
|
||||||
|
|
@ -71,16 +71,14 @@ When an attempt fails and you discover prerequisites, the branch must be cleaned
|
||||||
|
|
||||||
The branch between attempts should contain only documentation. Code returns when prerequisites are satisfied and the next attempt succeeds.
|
The branch between attempts should contain only documentation. Code returns when prerequisites are satisfied and the next attempt succeeds.
|
||||||
|
|
||||||
### Build artifacts and tags
|
### Build artifacts
|
||||||
|
|
||||||
Mikado resets apply to branch code, not build artifacts. Container images in the registry and git tags created by `container-tag-and-release` are independent of branch lifecycle:
|
Mikado resets apply to branch code, not build artifacts. Container images in the registry are independent of branch lifecycle:
|
||||||
|
|
||||||
- **Git tags** point to commit SHAs, not branches — they survive branch deletion and force-pushes.
|
- **Registry images** are build outputs cached in zot — tagged with commit SHAs, so each build is unique and traceable.
|
||||||
- **Registry images** are build outputs cached in zot — a wrong image is overwritten by the next release.
|
- **Automatic builds** trigger when container changes merge to main. Use `mise run container-build-and-release` for manual dispatch.
|
||||||
- **If a build succeeds but deployment fails**, the image is fine; the problem is elsewhere. Document what you learned, bump the version, and try again.
|
- **If a build succeeds but deployment fails**, the image is fine; the problem is elsewhere. Document what you learned and try again.
|
||||||
- **If a build fails in CI**, no image is pushed. Delete the git tag (`git tag -d <tag> && git push --delete origin <tag>`) and fix the nix/dockerfile before re-releasing.
|
- **If a build fails in CI**, no image is pushed. Fix the nix/dockerfile and re-merge or re-dispatch.
|
||||||
|
|
||||||
Tag freely during leaf node work. The build IS the verification step — deferring it creates a chicken-and-egg where the card can't be marked complete without a built image.
|
|
||||||
|
|
||||||
## Card Conventions
|
## Card Conventions
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ Discovered while attempting [[deploy-authentik]]: the deployment references `reg
|
||||||
|
|
||||||
1. Verify `containers/authentik/default.nix` builds — locally via Dagger (`dagger call build-nix --src=. --container-name=authentik`) or on ringtail (the CI nix builder runs there)
|
1. Verify `containers/authentik/default.nix` builds — locally via Dagger (`dagger call build-nix --src=. --container-name=authentik`) or on ringtail (the CI nix builder runs there)
|
||||||
2. The `ak` entrypoint needs bash (included via `bashInteractive`) and orchestrates both `server` and `worker` subcommands
|
2. The `ak` entrypoint needs bash (included via `bashInteractive`) and orchestrates both `server` and `worker` subcommands
|
||||||
3. Tag and release: `mise run container-tag-and-release authentik v1.0.0`
|
3. Trigger build: `mise run container-build-and-release authentik`
|
||||||
4. Verify the `-nix` tagged image appears in the registry
|
4. Verify the `-nix` tagged image appears in the registry
|
||||||
|
|
||||||
## What We Learned
|
## What We Learned
|
||||||
|
|
|
||||||
|
|
@ -52,20 +52,23 @@ nix-build containers/<name>/default.nix -o result
|
||||||
|
|
||||||
## 3. Release
|
## 3. Release
|
||||||
|
|
||||||
Once the image builds cleanly, create a tagged release:
|
Container builds trigger automatically when changes to `containers/<name>/` are merged to `main`. Both workflows fire and each skips if the relevant build file is absent.
|
||||||
|
|
||||||
|
To trigger a manual build (e.g. from a branch or to rebuild at a specific commit):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mise run container-tag-and-release <name> v1.0.0
|
mise run container-build-and-release <name>
|
||||||
|
mise run container-build-and-release <name> --ref <commit-sha>
|
||||||
```
|
```
|
||||||
|
|
||||||
Use `--dry-run` to preview without creating tags.
|
Use `--dry-run` to preview without dispatching.
|
||||||
|
|
||||||
This creates a single git tag `<name>-v1.0.0` and pushes it. Both Forgejo workflows trigger on the tag — each checks for its build file and skips if not present:
|
|
||||||
|
|
||||||
| Build file | Workflow | Runner | Registry tag |
|
| Build file | Workflow | Runner | Registry tag |
|
||||||
|------------|----------|--------|--------------|
|
|------------|----------|--------|--------------|
|
||||||
| `Dockerfile` | `build-container.yaml` | `k8s` (indri) | `:v1.0.0` |
|
| `Dockerfile` | `build-container.yaml` | `k8s` (indri) | `:vX.Y.Z-<sha>` |
|
||||||
| `default.nix` | `build-container-nix.yaml` | `nix-container-builder` ([[ringtail]]) | `:v1.0.0-nix` |
|
| `default.nix` | `build-container-nix.yaml` | `nix-container-builder` ([[ringtail]]) | `:vX.Y.Z-<sha>-nix` |
|
||||||
|
|
||||||
|
The version (`X.Y.Z`) is extracted from `ARG CONTAINER_APP_VERSION=` in the Dockerfile or `version = "..."` in `default.nix`. The SHA is the short (7-char) commit hash.
|
||||||
|
|
||||||
Check available images and tags with:
|
Check available images and tags with:
|
||||||
|
|
||||||
|
|
@ -78,7 +81,7 @@ mise run container-list
|
||||||
Change the image reference in `argocd/manifests/<service>/deployment.yaml`:
|
Change the image reference in `argocd/manifests/<service>/deployment.yaml`:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
image: registry.ops.eblu.me/blumeops/<name>:v1.0.0
|
image: registry.ops.eblu.me/blumeops/<name>:vX.Y.Z-abc1234
|
||||||
```
|
```
|
||||||
|
|
||||||
Then deploy per [[deploy-k8s-service]].
|
Then deploy per [[deploy-k8s-service]].
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
---
|
---
|
||||||
title: Adopt Commit-Based Container Tags
|
title: Adopt Commit-Based Container Tags
|
||||||
modified: 2026-02-20
|
modified: 2026-02-20
|
||||||
status: active
|
|
||||||
requires:
|
requires:
|
||||||
- add-container-version-sync-check
|
- add-container-version-sync-check
|
||||||
tags:
|
tags:
|
||||||
|
|
@ -29,9 +28,9 @@ Currently, container builds trigger on git tags matching `<container>-vX.Y.Z`. T
|
||||||
### Triggers
|
### Triggers
|
||||||
|
|
||||||
1. **Merged changes to main** — any push to `main` that modifies files under `containers/<name>/` triggers builds for that container
|
1. **Merged changes to main** — any push to `main` that modifies files under `containers/<name>/` triggers builds for that container
|
||||||
2. **Manual workflow dispatch** — for ad-hoc builds (e.g., testing on a branch). Accepts two inputs:
|
2. **Manual workflow dispatch** — for ad-hoc builds. Accepts two inputs:
|
||||||
- `container` (required) — which container to build
|
- `container` (required) — which container to build
|
||||||
- `ref` (optional, string) — the source commit SHA to build, defaulting to `HEAD` of `main`
|
- `ref` (optional, string) — the source commit SHA to build, defaulting to `GITHUB_SHA`
|
||||||
|
|
||||||
Both the Dockerfile and Nix workflows fire for each trigger, each bailing out if the container lacks the relevant build file (same as today).
|
Both the Dockerfile and Nix workflows fire for each trigger, each bailing out if the container lacks the relevant build file (same as today).
|
||||||
|
|
||||||
|
|
@ -40,56 +39,43 @@ Both the Dockerfile and Nix workflows fire for each trigger, each bailing out if
|
||||||
Each container's version is extracted at build time from existing declarations — no separate VERSION file:
|
Each container's version is extracted at build time from existing declarations — no separate VERSION file:
|
||||||
|
|
||||||
- **Dockerfile builds**: parsed from `ARG CONTAINER_APP_VERSION=<value>` in the Dockerfile
|
- **Dockerfile builds**: parsed from `ARG CONTAINER_APP_VERSION=<value>` in the Dockerfile
|
||||||
- **Nix builds**: extracted via `dagger call nix-version` or `nix eval`
|
- **Nix builds**: extracted from `version = "..."` in `default.nix`, or `CONTAINER_APP_VERSION` from the Dockerfile, or `dagger call nix-version` for nixpkgs packages
|
||||||
|
|
||||||
The [[add-container-version-sync-check]] pre-commit check ensures these declarations stay in sync with `service-versions.yaml`. See [[pin-container-versions]] for the work to ensure every container has a parseable version.
|
The [[add-container-version-sync-check]] pre-commit check ensures these declarations stay in sync with `service-versions.yaml`. See [[pin-container-versions]] for the work to ensure every container has a parseable version.
|
||||||
|
|
||||||
### Image Tag Format
|
### Image Tag Format
|
||||||
|
|
||||||
The registry image tag encodes the app version and the exact source commit:
|
| Build type | Tag format | Example |
|
||||||
|
|------------|-----------|---------|
|
||||||
| Scenario | Dockerfile tag | Nix tag |
|
| Dockerfile | `vX.Y.Z-<sha>` | `v2.2.17-abc1234` |
|
||||||
|----------|---------------|---------|
|
| Nix | `vX.Y.Z-<sha>-nix` | `v2.17.0-abc1234-nix` |
|
||||||
| Main branch build | `vX.Y.Z-<sha>` and `vX.Y.Z-<sha>-main` | `vX.Y.Z-<sha>-nix` and `vX.Y.Z-<sha>-main-nix` |
|
|
||||||
| Manual dispatch | `vX.Y.Z-<sha>` | `vX.Y.Z-<sha>-nix` |
|
|
||||||
|
|
||||||
Where:
|
Where:
|
||||||
- `X.Y.Z` is the version of the most relevant bundled app (e.g., miniflux `2.2.5`, navidrome `0.53.3`)
|
- `X.Y.Z` is the version of the most relevant bundled app (e.g., miniflux `2.2.17`, navidrome `0.60.3`)
|
||||||
- `<sha>` is the short commit SHA of the source tree used for the build
|
- `<sha>` is the 7-char short commit SHA of the source tree used for the build
|
||||||
|
|
||||||
The `-main` tag indicates a build from the merged main branch, suitable for production deployment. Non-main builds (manual dispatch) omit this suffix.
|
|
||||||
|
|
||||||
### What This Replaces
|
### What This Replaces
|
||||||
|
|
||||||
- The `container-tag-and-release` mise task is **renamed and repurposed** to `container-build-and-release` — it triggers a manual workflow dispatch instead of creating git tags. It sends the current `HEAD` SHA so that it works from any branch, not just main
|
- The `container-tag-and-release` mise task is **replaced** by `container-build-and-release` — it triggers a manual workflow dispatch instead of creating git tags
|
||||||
- Git tags of the form `<container>-vX.Y.Z` are no longer used to trigger builds
|
- Git tags of the form `<container>-vX.Y.Z` are no longer used to trigger builds
|
||||||
- The `container-list` mise task should be updated to display the new tag format
|
- The `container-list` mise task displays the new tag format
|
||||||
|
|
||||||
## Key Files
|
## Key Files
|
||||||
|
|
||||||
| File | Change |
|
| File | Change |
|
||||||
|------|--------|
|
|------|--------|
|
||||||
| `.forgejo/workflows/build-container.yaml` | Replace tag trigger with path + dispatch triggers; compute version and SHA; push multiple tags |
|
| `.forgejo/workflows/build-container.yaml` | Replace tag trigger with path + dispatch triggers; compute version and SHA |
|
||||||
| `.forgejo/workflows/build-container-nix.yaml` | Same trigger changes; add `-nix` suffix to new tag format |
|
| `.forgejo/workflows/build-container-nix.yaml` | Same trigger changes; add `-nix` suffix to new tag format |
|
||||||
| `.dagger/src/blumeops_ci/main.py` | Accept SHA parameter; publish with new tag format |
|
| `.dagger/src/blumeops_ci/main.py` | Accept SHA parameter; publish with new tag format |
|
||||||
| `mise-tasks/container-build-and-release` | Rename from `container-tag-and-release`; trigger workflow dispatch with current HEAD SHA |
|
| `mise-tasks/container-build-and-release` | New task replacing `container-tag-and-release`; triggers workflow dispatch |
|
||||||
| `mise-tasks/container-list` | Update tag display for new format |
|
| `mise-tasks/container-list` | Updated tag display for new format |
|
||||||
| `docs/how-to/deployment/build-container-image.md` | Document new workflow |
|
| `docs/how-to/deployment/build-container-image.md` | Updated documentation |
|
||||||
|
|
||||||
## Interaction With Other Prereqs
|
## Interaction With Other Prereqs
|
||||||
|
|
||||||
- **[[enforce-tag-immutability]]** — Commit SHA tags are inherently unique, reducing the scope of immutability enforcement to the `-main` rolling tag (if that is treated as mutable/latest) or eliminating it entirely if `-main` tags are also SHA-qualified (as proposed above)
|
- **[[enforce-tag-immutability]]** — Commit SHA tags are inherently unique, reducing the scope of immutability enforcement
|
||||||
- **[[wire-ci-registry-auth]]** — Auth changes apply regardless of tagging scheme; no conflict
|
- **[[wire-ci-registry-auth]]** — Auth changes apply regardless of tagging scheme; no conflict
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
- [ ] Push to main modifying `containers/nettest/` triggers both Docker and Nix builds
|
|
||||||
- [ ] Resulting image tags match `vX.Y.Z-<sha>` and `vX.Y.Z-<sha>-main` format
|
|
||||||
- [ ] Nix tags have `-nix` suffix
|
|
||||||
- [ ] Manual workflow dispatch builds with correct tags (no `-main` suffix)
|
|
||||||
- [ ] `mise run container-list` shows new tag format
|
|
||||||
- [ ] Existing deployments referencing old tags still work (images not deleted)
|
|
||||||
|
|
||||||
## Related
|
## Related
|
||||||
|
|
||||||
- [[harden-zot-registry]] — Parent goal
|
- [[harden-zot-registry]] — Parent goal
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ BlumeOps operations are driven by mise tasks. Run `mise tasks` to list all avail
|
||||||
| `pr-comments` | Check unresolved PR comments during review |
|
| `pr-comments` | Check unresolved PR comments during review |
|
||||||
| `blumeops-tasks` | Find pending tasks from Todoist |
|
| `blumeops-tasks` | Find pending tasks from Todoist |
|
||||||
| `container-list` | View available container images and tags |
|
| `container-list` | View available container images and tags |
|
||||||
| `container-tag-and-release` | Release a new container image version |
|
| `container-build-and-release` | Trigger container build workflows |
|
||||||
| `dns-preview` | Preview DNS changes before applying |
|
| `dns-preview` | Preview DNS changes before applying |
|
||||||
| `dns-up` | Apply DNS changes via Pulumi |
|
| `dns-up` | Apply DNS changes via Pulumi |
|
||||||
| `tailnet-preview` | Preview Tailscale ACL changes |
|
| `tailnet-preview` | Preview Tailscale ACL changes |
|
||||||
|
|
|
||||||
142
mise-tasks/container-build-and-release
Executable file
142
mise-tasks/container-build-and-release
Executable file
|
|
@ -0,0 +1,142 @@
|
||||||
|
#!/usr/bin/env -S uv run --script
|
||||||
|
# /// script
|
||||||
|
# requires-python = ">=3.12"
|
||||||
|
# dependencies = ["typer>=0.15.0", "httpx>=0.28.0"]
|
||||||
|
# ///
|
||||||
|
#MISE description="Trigger container build workflows via Forgejo API"
|
||||||
|
#USAGE arg "<container>" help="Container name (directory under containers/)"
|
||||||
|
#USAGE flag "--ref <ref>" help="Commit SHA to build (defaults to current HEAD)"
|
||||||
|
#USAGE flag "--dry-run" help="Show what would be done without triggering"
|
||||||
|
"""Trigger container build workflows via Forgejo API dispatch.
|
||||||
|
|
||||||
|
Dispatches both Build Container and Build Container (Nix) workflows.
|
||||||
|
Each workflow checks for its build file and skips if not present.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
import typer
|
||||||
|
|
||||||
|
REGISTRY = "registry.ops.eblu.me"
|
||||||
|
FORGE_URL = "https://forge.ops.eblu.me"
|
||||||
|
FORGE_API = f"{FORGE_URL}/api/v1"
|
||||||
|
REPO = "eblume/blumeops"
|
||||||
|
FORGE_ACTIONS = f"{FORGE_URL}/{REPO}/actions"
|
||||||
|
|
||||||
|
WORKFLOWS = [
|
||||||
|
"build-container.yaml",
|
||||||
|
"build-container-nix.yaml",
|
||||||
|
]
|
||||||
|
|
||||||
|
app = typer.Typer(add_completion=False)
|
||||||
|
|
||||||
|
|
||||||
|
def git(*args: str) -> str:
|
||||||
|
result = subprocess.run(
|
||||||
|
["git", *args], capture_output=True, text=True, check=True
|
||||||
|
)
|
||||||
|
return result.stdout.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def get_forge_token() -> str:
|
||||||
|
result = subprocess.run(
|
||||||
|
["op", "read", "op://blumeops/w3663ffnvkewbftncqxtcpeavy/api-token"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
return result.stdout.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def list_containers() -> None:
|
||||||
|
typer.echo("Available containers:")
|
||||||
|
for d in sorted(Path("containers").iterdir()):
|
||||||
|
if not d.is_dir():
|
||||||
|
continue
|
||||||
|
types = []
|
||||||
|
if (d / "Dockerfile").exists():
|
||||||
|
types.append("dockerfile")
|
||||||
|
if (d / "default.nix").exists():
|
||||||
|
types.append("nix")
|
||||||
|
if types:
|
||||||
|
typer.echo(f" - {d.name} ({', '.join(types)})")
|
||||||
|
|
||||||
|
|
||||||
|
@app.command()
|
||||||
|
def main(
|
||||||
|
container: str = typer.Argument(help="Container name (directory under containers/)"),
|
||||||
|
ref: str = typer.Option("", "--ref", help="Commit SHA to build (defaults to current HEAD)"),
|
||||||
|
dry_run: bool = typer.Option(False, "--dry-run", help="Show what would be done without triggering"),
|
||||||
|
) -> None:
|
||||||
|
"""Trigger container build workflows via Forgejo API dispatch."""
|
||||||
|
container_dir = Path("containers") / container
|
||||||
|
has_dockerfile = (container_dir / "Dockerfile").exists()
|
||||||
|
has_nix = (container_dir / "default.nix").exists()
|
||||||
|
|
||||||
|
if not has_dockerfile and not has_nix:
|
||||||
|
typer.echo(f"Error: No Dockerfile or default.nix found in '{container_dir}'")
|
||||||
|
typer.echo()
|
||||||
|
list_containers()
|
||||||
|
raise typer.Exit(1)
|
||||||
|
|
||||||
|
if not ref:
|
||||||
|
ref = git("rev-parse", "HEAD")
|
||||||
|
|
||||||
|
short_sha = ref[:7]
|
||||||
|
image = f"blumeops/{container}"
|
||||||
|
|
||||||
|
# Show expected builds
|
||||||
|
builds = []
|
||||||
|
if has_dockerfile:
|
||||||
|
builds.append(f" dockerfile -> {REGISTRY}/{image}:v<version>-{short_sha}")
|
||||||
|
if has_nix:
|
||||||
|
builds.append(f" nix -> {REGISTRY}/{image}:v<version>-{short_sha}-nix")
|
||||||
|
|
||||||
|
if dry_run:
|
||||||
|
typer.echo("[dry-run mode]")
|
||||||
|
typer.echo(f"Container: {container}")
|
||||||
|
typer.echo(f"Commit: {ref} ({short_sha})")
|
||||||
|
typer.echo(f"Expected builds:")
|
||||||
|
for b in builds:
|
||||||
|
typer.echo(b)
|
||||||
|
typer.echo()
|
||||||
|
|
||||||
|
if dry_run:
|
||||||
|
typer.echo("[dry-run] Would dispatch workflows:")
|
||||||
|
for wf in WORKFLOWS:
|
||||||
|
typer.echo(f" - {wf}")
|
||||||
|
typer.echo()
|
||||||
|
typer.echo(f"Monitor builds at: {FORGE_ACTIONS}")
|
||||||
|
return
|
||||||
|
|
||||||
|
token = get_forge_token()
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"token {token}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
}
|
||||||
|
|
||||||
|
for wf in WORKFLOWS:
|
||||||
|
url = f"{FORGE_API}/repos/{REPO}/actions/workflows/{wf}/dispatches"
|
||||||
|
payload = {
|
||||||
|
"ref": "main",
|
||||||
|
"inputs": {
|
||||||
|
"container": container,
|
||||||
|
"ref": ref,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
resp = httpx.post(url, json=payload, headers=headers, timeout=30)
|
||||||
|
if resp.status_code == 204:
|
||||||
|
typer.echo(f"Dispatched {wf}")
|
||||||
|
else:
|
||||||
|
typer.echo(f"Error dispatching {wf}: {resp.status_code} {resp.text}")
|
||||||
|
raise typer.Exit(1)
|
||||||
|
|
||||||
|
typer.echo()
|
||||||
|
typer.echo(f"Monitor builds at: {FORGE_ACTIONS}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app()
|
||||||
|
|
@ -52,10 +52,11 @@ for dir in "$CONTAINER_DIR"/*/; do
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "---"
|
echo "---"
|
||||||
echo "To release a new version:"
|
echo "To trigger a build:"
|
||||||
echo " mise run container-tag-and-release <container> <version>"
|
echo " mise run container-build-and-release <container>"
|
||||||
echo ""
|
echo ""
|
||||||
echo "One tag triggers all applicable workflows (dockerfile and/or nix)."
|
echo "Dispatches both Dockerfile and Nix workflows (each skips if build file absent)."
|
||||||
|
echo "Tags: vX.Y.Z-<sha> (Dockerfile), vX.Y.Z-<sha>-nix (Nix)"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Example:"
|
echo "Example:"
|
||||||
echo " mise run container-tag-and-release nettest v1.0.0"
|
echo " mise run container-build-and-release nettest"
|
||||||
|
|
|
||||||
|
|
@ -1,114 +0,0 @@
|
||||||
#!/usr/bin/env -S uv run --script
|
|
||||||
# /// script
|
|
||||||
# requires-python = ">=3.12"
|
|
||||||
# dependencies = ["typer>=0.15.0"]
|
|
||||||
# ///
|
|
||||||
#MISE description="Release a container image by creating a git tag"
|
|
||||||
#USAGE arg "<container>" help="Container name (directory under containers/)"
|
|
||||||
#USAGE arg "<version>" help="Version in vX.Y.Z format"
|
|
||||||
#USAGE flag "--dry-run" help="Show what would be done without creating tags"
|
|
||||||
"""Release a container image by creating a git tag that triggers CI builds.
|
|
||||||
|
|
||||||
One tag triggers all applicable workflows:
|
|
||||||
- Dockerfile present -> Build Container workflow -> :v<version>
|
|
||||||
- default.nix present -> Build Container (Nix) workflow -> :v<version>-nix
|
|
||||||
"""
|
|
||||||
|
|
||||||
import re
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import typer
|
|
||||||
|
|
||||||
REGISTRY = "registry.ops.eblu.me"
|
|
||||||
FORGE_ACTIONS = "https://forge.ops.eblu.me/eblume/blumeops/actions"
|
|
||||||
|
|
||||||
app = typer.Typer(add_completion=False)
|
|
||||||
|
|
||||||
|
|
||||||
def git(*args: str) -> str:
|
|
||||||
result = subprocess.run(
|
|
||||||
["git", *args], capture_output=True, text=True, check=True
|
|
||||||
)
|
|
||||||
return result.stdout.strip()
|
|
||||||
|
|
||||||
|
|
||||||
def git_tag_exists(tag: str) -> bool:
|
|
||||||
result = subprocess.run(
|
|
||||||
["git", "rev-parse", tag], capture_output=True, text=True
|
|
||||||
)
|
|
||||||
return result.returncode == 0
|
|
||||||
|
|
||||||
|
|
||||||
def list_containers() -> None:
|
|
||||||
typer.echo("Available containers:")
|
|
||||||
for d in sorted(Path("containers").iterdir()):
|
|
||||||
if not d.is_dir():
|
|
||||||
continue
|
|
||||||
types = []
|
|
||||||
if (d / "Dockerfile").exists():
|
|
||||||
types.append("dockerfile")
|
|
||||||
if (d / "default.nix").exists():
|
|
||||||
types.append("nix")
|
|
||||||
if types:
|
|
||||||
typer.echo(f" - {d.name} ({', '.join(types)})")
|
|
||||||
|
|
||||||
|
|
||||||
@app.command()
|
|
||||||
def main(
|
|
||||||
container: str = typer.Argument(help="Container name (directory under containers/)"),
|
|
||||||
version: str = typer.Argument(help="Version in vX.Y.Z format"),
|
|
||||||
dry_run: bool = typer.Option(False, "--dry-run", help="Show what would be done without creating tags"),
|
|
||||||
) -> None:
|
|
||||||
"""Release a container image by creating a git tag that triggers CI builds."""
|
|
||||||
if not re.match(r"^v\d+\.\d+\.\d+$", version):
|
|
||||||
typer.echo("Error: Version must be in format vX.Y.Z (e.g. v1.0.0)")
|
|
||||||
raise typer.Exit(1)
|
|
||||||
|
|
||||||
container_dir = Path("containers") / container
|
|
||||||
has_dockerfile = (container_dir / "Dockerfile").exists()
|
|
||||||
has_nix = (container_dir / "default.nix").exists()
|
|
||||||
|
|
||||||
if not has_dockerfile and not has_nix:
|
|
||||||
typer.echo(f"Error: No Dockerfile or default.nix found in '{container_dir}'")
|
|
||||||
typer.echo()
|
|
||||||
list_containers()
|
|
||||||
raise typer.Exit(1)
|
|
||||||
|
|
||||||
image = f"blumeops/{container}"
|
|
||||||
tag = f"{container}-{version}"
|
|
||||||
|
|
||||||
# Show what workflows will trigger
|
|
||||||
builds = []
|
|
||||||
if has_dockerfile:
|
|
||||||
builds.append(f" dockerfile -> {REGISTRY}/{image}:{version}")
|
|
||||||
if has_nix:
|
|
||||||
builds.append(f" nix -> {REGISTRY}/{image}:{version}-nix")
|
|
||||||
|
|
||||||
if dry_run:
|
|
||||||
typer.echo("[dry-run mode]")
|
|
||||||
typer.echo(f"Container: {container}")
|
|
||||||
typer.echo(f"Tag: {tag}")
|
|
||||||
typer.echo(f"Builds:")
|
|
||||||
for b in builds:
|
|
||||||
typer.echo(b)
|
|
||||||
typer.echo()
|
|
||||||
|
|
||||||
if git_tag_exists(tag):
|
|
||||||
typer.echo(f"Error: Tag '{tag}' already exists")
|
|
||||||
raise typer.Exit(1)
|
|
||||||
|
|
||||||
if dry_run:
|
|
||||||
typer.echo(f"[dry-run] Would create and push tag: {tag}")
|
|
||||||
else:
|
|
||||||
git("tag", tag)
|
|
||||||
git("push", "origin", tag)
|
|
||||||
typer.echo(f"Tag '{tag}' created and pushed")
|
|
||||||
|
|
||||||
typer.echo()
|
|
||||||
typer.echo(f"Monitor builds at: {FORGE_ACTIONS}")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
app()
|
|
||||||
|
|
@ -168,7 +168,7 @@ def main(
|
||||||
checklist_parts += [
|
checklist_parts += [
|
||||||
"\n[bold]Custom Container (hybrid):[/bold]\n",
|
"\n[bold]Custom Container (hybrid):[/bold]\n",
|
||||||
"• Check base image for updates\n",
|
"• Check base image for updates\n",
|
||||||
"• Rebuild container if needed: mise run container-tag-and-release\n",
|
"• Rebuild container if needed: mise run container-build-and-release\n",
|
||||||
"• Update ArgoCD manifest with new image tag\n",
|
"• Update ArgoCD manifest with new image tag\n",
|
||||||
]
|
]
|
||||||
elif svc_type == "argocd":
|
elif svc_type == "argocd":
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue