Role Authoring

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:

LabelValuePurpose
jackin.construct.versionThe 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.shaThe short (7-char) git commit SHA of the role repo at build timeVersion 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.

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:

.github/workflows/publish-image.yml
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, and jackin-role publish-labels to extract the image repository and label set from the role contract.
  • Applies jackin.construct.version and jackin.role.git.sha as OCI labels at build time via docker/build-push-action's labels: input — no ARG or LABEL declarations needed in your Dockerfile.
  • Builds separate image manifests for linux/amd64 and linux/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

  1. Determine the construct version

    Read the version from your Dockerfile's FROM line. The version is the part between : and - in the construct tag:

    FROM projectjackin/construct:0.4-trixie
    #                            ^^^— this is the construct version

    If the jackin-role binary 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 FROM line:

    CONSTRUCT_VERSION=$(grep -m1 '^FROM projectjackin/construct:' Dockerfile \
      | sed 's|FROM projectjackin/construct:\([^-]*\)-.*|\1|')
  2. Get the role commit SHA

    ROLE_GIT_SHA=$(git rev-parse HEAD)
  3. Build the image with both labels

    Pass both labels using --label flags:

    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 ARG or LABEL declarations 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

.gitlab-ci.yml
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:

jackin.role.toml
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_VERSION or ARG ROLE_GIT_SHA declarations are needed. The workflow or --label flags at build time handle both.
  • The construct version jackin reads for staleness detection comes from the FROM line — not from a label or ARG. Keep the version tag in the FROM line accurate and let Renovate track updates.

On this page