Publishing Role Images
What jackin requires from a built agent role image and how to build one with or without the provided GitHub Actions workflow
This page is for role authors — anyone who needs to build and publish a Docker image from their role repository. If you are hosting your role on GitHub and use the standard jackin-project/jackin-role-action reusable workflow, most of this happens automatically. Read the Using the provided workflow section to confirm the setup, and the Custom CI/CD section only if you build outside GitHub Actions.
Required OCI labels
Every published role image must carry two OCI labels that jackin reads at launch time:
| Label | Value | Purpose |
|---|---|---|
jackin.construct.version | The version tag from the FROM line (e.g. 0.2) | Staleness detection — jackin warns when the published image was built from an older construct than the one currently resolved for a new build |
jackin.role.git.sha | The short (7-char) git commit SHA of the role repo at build time | Version display and staleness tracking — compared against the role checkout's HEAD |
If either label is missing or set to unknown, jackin❯ treats the image as built without version tracking. Staleness warnings are suppressed — jackin❯ cannot detect that the published image is behind. The image still loads and runs, but the operator loses visibility into whether they are running a stale agent.
Keys use dotted namespacing (jackin.construct.version, jackin.role.git.sha). jackin-role publish-labels --role-git-sha <sha> prints this label set for workflows and custom CI so the runtime freshness contract stays in the binary instead of duplicated in YAML. Until your role re-publishes with the updated jackin-role-action, jackin falls back to a local workspace rebuild for that role (correct, just not the fast published path). See Image Labels & Recipe Hash for the full label schema jackin verifies.
Using the provided workflow (recommended)
Role repositories on GitHub should call the jackin-project/jackin-role-action reusable workflow. It handles label injection, Docker layer cache ordering, GitHub API authentication, multi-platform builds (linux/amd64 and linux/arm64), image signing, and manifest assembly automatically.
A minimal caller workflow in your role repo:
name: Publish Image
on:
push:
branches: [main]
paths:
- 'Dockerfile'
- 'jackin.role.toml'
- '.github/workflows/publish-image.yml'
workflow_dispatch:
jobs:
publish:
uses: jackin-project/jackin-role-action/.github/workflows/publish.yml@<SHA> # pin to a release SHA
permissions:
contents: read
id-token: write
secrets:
registry-username: ${{ secrets.DOCKERHUB_USERNAME }}
registry-password: ${{ secrets.DOCKERHUB_TOKEN }}
gh-readonly-token: ${{ secrets.GH_READONLY_TOKEN }}Replace <SHA> with the pinned commit SHA from the jackin-role-action releases. Renovate can keep the pin current automatically — the generated role repo scaffold includes a Renovate configuration with the right pattern.
What the workflow does automatically:
- Runs
jackin-role validate,jackin-role published-image-repository, andjackin-role publish-labelsto extract the image repository and label set from the role contract. - Applies
jackin.construct.versionandjackin.role.git.shaas OCI labels at build time viadocker/build-push-action'slabels:input — noARGorLABELdeclarations needed in your Dockerfile. - Builds separate image manifests for
linux/amd64andlinux/arm64. - Assembles and pushes a multi-platform manifest list.
- Signs the image with Sigstore cosign.
Your Dockerfile only needs to declare the tools your role installs. The FROM projectjackin/construct:<version>-<distro> line is the construct version source of truth — the workflow reads it automatically.
Custom CI/CD
If your role repo is not on GitHub, or if you use a different CI/CD system (GitLab CI, Jenkins, Buildkite, CircleCI, etc.), you must apply the two labels yourself at build time.
Minimum required steps
-
Determine the construct version
Read the version from your Dockerfile's
FROMline. The version is the part between:and-in the construct tag:FROM projectjackin/construct:0.4-trixie # ^^^— this is the construct versionIf the
jackin-rolebinary is available in your CI environment (download it from the jackin releases), prefer asking it for the complete label set:ROLE_GIT_SHA=$(git rev-parse HEAD) jackin-role publish-labels --role-git-sha "${ROLE_GIT_SHA}" .Otherwise, parse it directly from the
FROMline:CONSTRUCT_VERSION=$(grep -m1 '^FROM projectjackin/construct:' Dockerfile \ | sed 's|FROM projectjackin/construct:\([^-]*\)-.*|\1|') -
Get the role commit SHA
ROLE_GIT_SHA=$(git rev-parse HEAD) -
Build the image with both labels
Pass both labels using
--labelflags:CONSTRUCT_VERSION=$(jackin-role construct-version .) docker build \ --label "jackin.construct.version=${CONSTRUCT_VERSION}" \ --label "jackin.role.git.sha=${ROLE_GIT_SHA}" \ -t your-registry/your-role:latest \ .No
ARGorLABELdeclarations are needed in the Dockerfile.
Layer cache ordering
ARG instructions in a Dockerfile invalidate all subsequent layers when the argument value changes. A git SHA changes on every commit — so if you declare ARG ROLE_GIT_SHA inside the Dockerfile, every commit busts the layer cache for everything below it.
Using --label at build time (or the labels: input in docker/build-push-action) bypasses this: the label is applied to the final image manifest without touching the Dockerfile instruction stream. Your expensive tool-installation RUN layers remain cached across commits.
If you are migrating from an older role repo that has ARG CONSTRUCT_VERSION/LABEL jackin.construct.version and ARG ROLE_GIT_SHA/LABEL jackin.role.git.sha in the Dockerfile, remove those declarations and switch to --label flags at build time. The FROM line is still how jackin reads the construct version — it does not need to be re-declared as an ARG.
Example: GitLab CI
build-and-push:
stage: build
image: docker:27
services:
- docker:27-dind
script:
- CONSTRUCT_VERSION=$(grep -m1 '^FROM projectjackin/construct:' Dockerfile | sed 's|FROM projectjackin/construct:\([^-]*\)-.*|\1|')
- ROLE_GIT_SHA=${CI_COMMIT_SHA}
- docker login -u "${REGISTRY_USERNAME}" -p "${REGISTRY_PASSWORD}"
- >
docker build
--label "jackin.construct.version=${CONSTRUCT_VERSION}"
--label "jackin.role.git.sha=${ROLE_GIT_SHA}"
-t "${REGISTRY_IMAGE}:latest"
.
- docker push "${REGISTRY_IMAGE}:latest"Using a different registry
The reusable workflow accepts a registry input (defaults to Docker Hub) and matching registry-username / registry-password secrets. To publish to a different registry, pass the appropriate values:
jobs:
publish:
uses: jackin-project/jackin-role-action/.github/workflows/publish.yml@<SHA>
permissions:
contents: read
id-token: write
packages: write
secrets:
registry-username: ${{ github.actor }}
registry-password: ${{ secrets.GITHUB_TOKEN }}Set your jackin.role.toml published_image to the full GHCR path:
published_image = "ghcr.io/your-org/jackin-your-role"jobs:
publish:
uses: jackin-project/jackin-role-action/.github/workflows/publish.yml@<SHA>
permissions:
contents: read
id-token: write
secrets:
registry: 123456789.dkr.ecr.us-east-1.amazonaws.com
registry-username: AWS
registry-password: ${{ steps.ecr-login.outputs.token }}Obtain the ECR token in a preceding step using the AWS CLI or the aws-actions/amazon-ecr-login action, then pass it as registry-password. Set published_image in your manifest to the full ECR URI.
jobs:
publish:
uses: jackin-project/jackin-role-action/.github/workflows/publish.yml@<SHA>
with:
registry: registry.your-org.internal
secrets:
registry-username: ${{ secrets.REGISTRY_USER }}
registry-password: ${{ secrets.REGISTRY_PASS }}jackin❯ must be able to pull the image at launch time. If you publish to a private registry, the credentials must be available in the operator's Docker environment before running jackin load or jackin console. Configure Docker credentials on the operator's machine using docker login <registry> or a credential helper.
Minimum Dockerfile requirements
- The final stage must start from
projectjackin/construct:<version>-<distro>(see Role Repositories for the full contract). - No
ARG CONSTRUCT_VERSIONorARG ROLE_GIT_SHAdeclarations are needed. The workflow or--labelflags at build time handle both. - The construct version jackin reads for staleness detection comes from the
FROMline — not from a label or ARG. Keep the version tag in theFROMline accurate and let Renovate track updates.