From bc055b245e34c960646cf3668f5e8d965f9ef628 Mon Sep 17 00:00:00 2001 From: ezynda3 Date: Fri, 26 Jun 2026 16:59:24 +0300 Subject: [PATCH 1/3] feat(sandbox): add official Kit sandbox image + GHCR workflow Pre-baked Linux image for running the Kit coding agent inside a workdir.dev sandbox, so the dev-sandbox integration no longer installs the toolchain on every boot. - deploy/sandbox/Dockerfile: ubuntu:24.04 mirroring workdir's curated base apt layer, plus Go (go install github.com/mark3labs/kit/cmd/kit), the gh/glab/tea git-forge CLIs, git+openssh-client, the 127.0.0.1 localhost /etc/hosts entry Kit's OAuth listener needs, and /workspace. Deliberately omits workdir's guest-agent/init (the custom-image builder injects those). - .github/workflows/sandbox-image.yml: buildx -> GHCR (ghcr.io/mark3labs/ kit-sandbox) on master pushes touching deploy/sandbox/**, releases, and workflow_dispatch; tags latest/sha/semver. - deploy/sandbox/README.md: build, publish, and workdir POST /v1/images (source.type=oci) registration instructions. Versions pinned (overridable via build args): go 1.26.4, gh 2.95.0, glab 1.105.0, tea 0.14.1. Builds verified locally on linux/amd64. --- .github/workflows/sandbox-image.yml | 92 +++++++++++++++++++++++++ deploy/sandbox/Dockerfile | 100 ++++++++++++++++++++++++++++ deploy/sandbox/README.md | 82 +++++++++++++++++++++++ 3 files changed, 274 insertions(+) create mode 100644 .github/workflows/sandbox-image.yml create mode 100644 deploy/sandbox/Dockerfile create mode 100644 deploy/sandbox/README.md diff --git a/.github/workflows/sandbox-image.yml b/.github/workflows/sandbox-image.yml new file mode 100644 index 00000000..3a8d7e56 --- /dev/null +++ b/.github/workflows/sandbox-image.yml @@ -0,0 +1,92 @@ +name: Sandbox Image + +# Build the official Kit sandbox image and publish it to GHCR. The image is a +# ready-to-run Linux userland (Go + kit + gh/glab/tea + git/ssh) intended to be +# registered as a workdir.dev custom image: +# +# POST /v1/images +# { "source": { "type": "oci", +# "image_ref": "ghcr.io/mark3labs/kit-sandbox:latest" }, +# "name": "custom/mark3labs/kit-sandbox" } +# +# Then create sandboxes with { "image": "custom/mark3labs/kit-sandbox", ... }. + +on: + push: + branches: [master] + paths: + - "deploy/sandbox/**" + - ".github/workflows/sandbox-image.yml" + # Rebuild on every release so the image tracks the latest kit binary. + release: + types: [published] + workflow_dispatch: + inputs: + kit_version: + description: "kit version to install (go install ...@VERSION)" + required: false + default: "latest" + +concurrency: + group: sandbox-image-${{ github.ref }} + cancel-in-progress: true + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }}-sandbox # ghcr.io/mark3labs/kit-sandbox + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v4 + + - name: Resolve kit version + id: kitver + run: | + if [ "${{ github.event_name }}" = "release" ]; then + echo "version=${{ github.event.release.tag_name }}" >> "$GITHUB_OUTPUT" + elif [ -n "${{ inputs.kit_version }}" ]; then + echo "version=${{ inputs.kit_version }}" >> "$GITHUB_OUTPUT" + else + echo "version=latest" >> "$GITHUB_OUTPUT" + fi + + - uses: docker/setup-buildx-action@v3 + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Docker metadata (tags + labels) + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=raw,value=latest,enable={{is_default_branch}} + type=ref,event=branch + type=sha,format=short + type=semver,pattern={{version}},event=release + type=semver,pattern={{major}}.{{minor}},event=release + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + file: deploy/sandbox/Dockerfile + # workdir runs x86_64 Firecracker microVMs. + platforms: linux/amd64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + KIT_VERSION=${{ steps.kitver.outputs.version }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/deploy/sandbox/Dockerfile b/deploy/sandbox/Dockerfile new file mode 100644 index 00000000..58115940 --- /dev/null +++ b/deploy/sandbox/Dockerfile @@ -0,0 +1,100 @@ +# syntax=docker/dockerfile:1 +# +# Official Kit sandbox image. +# +# A ready-to-run Linux userland for the Kit coding agent, designed to be +# registered as a workdir.dev custom image (`POST /v1/images` with +# `source.type = "oci"`). It pre-bakes the toolchain that the dev-sandbox +# integration otherwise installs at runtime on every boot: +# +# * Go (compiler + toolchain) and the Kit CLI (`kit`) +# * The three git-forge CLIs: gh (GitHub), glab (GitLab), tea (Gitea) +# * git + openssh-client for SSH-based clones +# * the `127.0.0.1 localhost` /etc/hosts entry Kit's OAuth listener needs +# +# The base apt layer mirrors workdir's curated base image +# (deploy/images/base/Dockerfile in mv37-org/workdir). We deliberately do NOT +# COPY in workdir's `sandbox-guest-agent` / `sandbox-init`: workdir's custom +# image builder injects the (static musl) guest agent + init itself when it +# converts this OCI image to an ext4 rootfs. +# +# Build (multi-arch capable; workdir runs x86_64 Firecracker): +# docker buildx build --platform linux/amd64 -f deploy/sandbox/Dockerfile -t kit-sandbox . +# +FROM ubuntu:24.04 + +ENV DEBIAN_FRONTEND=noninteractive + +# Pinned tool versions — override at build time with --build-arg. +ARG GO_VERSION=1.26.4 +ARG KIT_VERSION=latest +ARG GH_VERSION=2.95.0 +ARG GLAB_VERSION=1.105.0 +ARG TEA_VERSION=0.14.1 + +# Resolved automatically by buildx (linux/amd64 -> amd64, linux/arm64 -> arm64). +ARG TARGETARCH=amd64 + +# Base userland — mirrors workdir's curated base image apt layer, plus +# openssh-client (SSH clones) and unzip/jq/sudo conveniences. +RUN apt-get update && apt-get install -y --no-install-recommends \ + bash coreutils curl wget ca-certificates git openssh-client \ + socat iproute2 iputils-ping unzip jq sudo \ + python3 python3-pip python3-venv nodejs npm build-essential \ + && rm -rf /var/lib/apt/lists/* + +# Go toolchain -> /usr/local/go, on PATH for all later layers and at runtime. +RUN curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-${TARGETARCH}.tar.gz" \ + -o /tmp/go.tgz \ + && tar -C /usr/local -xzf /tmp/go.tgz \ + && rm /tmp/go.tgz +ENV PATH=/usr/local/go/bin:/usr/local/bin:/root/go/bin:$PATH +ENV GOPATH=/root/go +ENV GOTOOLCHAIN=auto + +# Kit CLI — compiled from source and installed onto PATH as `kit`. Inject the +# version ldflag (matching .goreleaser.yaml) so `kit --version` reports the +# build instead of "dev"; on release builds KIT_VERSION is the real tag. +RUN GOBIN=/usr/local/bin go install \ + -ldflags "-s -w -X main.version=${KIT_VERSION}" \ + "github.com/mark3labs/kit/cmd/kit@${KIT_VERSION}" \ + && kit --version || true + +# GitHub CLI (gh). +RUN curl -fsSL "https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_${TARGETARCH}.tar.gz" \ + -o /tmp/gh.tgz \ + && tar -C /tmp -xzf /tmp/gh.tgz \ + && install -m755 "/tmp/gh_${GH_VERSION}_linux_${TARGETARCH}/bin/gh" /usr/local/bin/gh \ + && rm -rf /tmp/gh.tgz "/tmp/gh_${GH_VERSION}_linux_${TARGETARCH}" + +# GitLab CLI (glab). +RUN curl -fsSL "https://gitlab.com/gitlab-org/cli/-/releases/v${GLAB_VERSION}/downloads/glab_${GLAB_VERSION}_linux_${TARGETARCH}.tar.gz" \ + -o /tmp/glab.tgz \ + && mkdir -p /tmp/glab \ + && tar -C /tmp/glab -xzf /tmp/glab.tgz \ + && install -m755 /tmp/glab/bin/glab /usr/local/bin/glab \ + && rm -rf /tmp/glab.tgz /tmp/glab + +# Gitea CLI (tea) — single static binary. +RUN curl -fsSL "https://dl.gitea.com/tea/${TEA_VERSION}/tea-${TEA_VERSION}-linux-${TARGETARCH}" \ + -o /usr/local/bin/tea \ + && chmod +x /usr/local/bin/tea + +# Kit's OAuth callback listener binds localhost; without this entry it fails to +# create the listener and nil-panics on cleanup. Bake it in so the runtime +# doesn't have to patch /etc/hosts on every boot. +RUN grep -q localhost /etc/hosts || echo '127.0.0.1 localhost' >> /etc/hosts + +# Default workspace the agent clones into. +RUN mkdir -p /workspace +WORKDIR /workspace + +# Smoke-check that every CLI resolves on PATH at build time. +RUN set -e; \ + go version; \ + kit --version || true; \ + gh --version; \ + glab --version; \ + tea --version + +CMD ["/bin/bash"] diff --git a/deploy/sandbox/README.md b/deploy/sandbox/README.md new file mode 100644 index 00000000..a60677d2 --- /dev/null +++ b/deploy/sandbox/README.md @@ -0,0 +1,82 @@ +# Kit sandbox image + +An official, pre-baked Linux image for running the [Kit](https://github.com/mark3labs/kit) +coding agent inside a [workdir.dev](https://workdir.dev) sandbox. + +It exists so the dev-sandbox integration no longer has to `apt-get install` and +`go install` the toolchain on **every** sandbox boot — everything is baked in. + +## What's inside + +Based on `ubuntu:24.04`, mirroring workdir's curated base apt layer, plus: + +| Tool | Purpose | +| --- | --- | +| **Go** (`/usr/local/go`) | toolchain for building/running Go code | +| **kit** | the Kit coding agent CLI (`go install github.com/mark3labs/kit/cmd/kit`) | +| **gh** | GitHub CLI — open PRs, manage repos | +| **glab** | GitLab CLI — open MRs | +| **tea** | Gitea CLI | +| **git**, **openssh-client** | SSH-based clones | +| python3, node/npm, build-essential, jq, curl, … | general dev userland | + +It also bakes the `127.0.0.1 localhost` `/etc/hosts` entry Kit's OAuth listener +requires, and creates `/workspace`. + +> **Note:** this image intentionally does **not** ship workdir's +> `sandbox-guest-agent` / `sandbox-init`. workdir's custom-image builder injects +> the (static musl) guest agent and init when it converts this OCI image to an +> ext4 rootfs. + +## Build locally + +```bash +# from the repo root +docker buildx build --platform linux/amd64 \ + -f deploy/sandbox/Dockerfile \ + -t kit-sandbox . +``` + +Override pinned versions with `--build-arg` (`GO_VERSION`, `KIT_VERSION`, +`GH_VERSION`, `GLAB_VERSION`, `TEA_VERSION`). + +## CI / publishing + +`.github/workflows/sandbox-image.yml` builds the image and pushes it to GHCR +(`ghcr.io/mark3labs/kit-sandbox`) on: + +- pushes to `master` touching `deploy/sandbox/**`, +- published releases (the image is rebuilt against the released `kit` tag), +- manual `workflow_dispatch` (optionally pinning `kit_version`). + +Tags published: `latest` (default branch), `sha-`, branch name, and +`vX.Y.Z` / `vX.Y` on releases. + +## Register it as a workdir custom image + +Once published, register the OCI image with workdir +([API reference](https://github.com/mv37-org/workdir/blob/main/docs/API.md#images-spec-10-11)): + +```bash +curl -fsSL -X POST https://api.workdir.dev/v1/images \ + -H "Authorization: Bearer $WORKDIR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "source": { "type": "oci", + "image_ref": "ghcr.io/mark3labs/kit-sandbox:latest" }, + "name": "custom/mark3labs/kit-sandbox", + "resources_hint": { "cpu": 2, "memory_mb": 4096, "disk_gb": 16 } + }' +``` + +The build is asynchronous (`202 Accepted`); poll `GET /v1/images/:id` for status +and `build_log`. Then create sandboxes against it: + +```jsonc +POST /v1/sandboxes +{ "image": "custom/mark3labs/kit-sandbox", + "image_version": "2026-06-10-ab12cd" } +``` + +> If the GHCR package is private, grant workdir's builder pull access (make the +> package public, or configure registry credentials on the workdir side). From e0ce63bbb57ab0057b15ed0d7aa387cc064fc63c Mon Sep 17 00:00:00 2001 From: ezynda3 Date: Fri, 26 Jun 2026 17:16:44 +0300 Subject: [PATCH 2/3] fix(sandbox): address CodeRabbit review - Pin all workflow actions to immutable commit SHAs + persist-credentials: false on checkout (prevents upstream retag / credential persistence on a job with packages: write). - Resolve-kit-version step: move github context values into env: and validate the version before writing GITHUB_OUTPUT (avoids template-injection into a shell step with publish credentials). - metadata-action: add prefix=v to the semver tag patterns so release image tags are vX.Y.Z / vX.Y as documented (the action strips the v otherwise). - Dockerfile: verify Go, gh, glab, tea downloads against pinned SHA256 checksums; pin the image to linux/amd64 (checksums are arch-specific and workdir runs x86_64 Firecracker). - Dockerfile: drop '|| true' on the kit smoke checks so a broken kit binary fails the build instead of publishing green. Rebuilt locally: all checksums verify OK, kit version injects, smoke check passes. --- .github/workflows/sandbox-image.yml | 41 ++++++++++++++++++++--------- deploy/sandbox/Dockerfile | 33 ++++++++++++++++------- 2 files changed, 53 insertions(+), 21 deletions(-) diff --git a/.github/workflows/sandbox-image.yml b/.github/workflows/sandbox-image.yml index 3a8d7e56..5b7d705d 100644 --- a/.github/workflows/sandbox-image.yml +++ b/.github/workflows/sandbox-image.yml @@ -42,23 +42,38 @@ jobs: contents: read packages: write steps: - - uses: actions/checkout@v4 + # Actions are pinned to immutable commit SHAs (comments preserve the + # human-readable version) so an upstream retag can't alter this + # package-publishing pipeline. + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + persist-credentials: false - name: Resolve kit version id: kitver + env: + EVENT_NAME: ${{ github.event_name }} + RELEASE_TAG: ${{ github.event.release.tag_name || '' }} + INPUT_KIT_VERSION: ${{ inputs.kit_version || '' }} run: | - if [ "${{ github.event_name }}" = "release" ]; then - echo "version=${{ github.event.release.tag_name }}" >> "$GITHUB_OUTPUT" - elif [ -n "${{ inputs.kit_version }}" ]; then - echo "version=${{ inputs.kit_version }}" >> "$GITHUB_OUTPUT" + if [ "$EVENT_NAME" = "release" ]; then + version="$RELEASE_TAG" + elif [ -n "$INPUT_KIT_VERSION" ]; then + version="$INPUT_KIT_VERSION" else - echo "version=latest" >> "$GITHUB_OUTPUT" + version="latest" fi + # Reject anything that could smuggle shell/newlines into later steps. + case "$version" in + *$'\n'*|*$'\r'*|*' '*|*';'*|*'&'*|*'|'*|*'$'*|*'`'*) + echo "invalid kit version: $version" >&2; exit 1 ;; + esac + printf 'version=%s\n' "$version" >> "$GITHUB_OUTPUT" - - uses: docker/setup-buildx-action@v3 + - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - name: Log in to GHCR - uses: docker/login-action@v3 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -66,18 +81,20 @@ jobs: - name: Docker metadata (tags + labels) id: meta - uses: docker/metadata-action@v5 + uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5 with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + # prefix=v keeps release image tags as vX.Y.Z / vX.Y; without it + # docker/metadata-action strips the leading v (v1.2.3 -> 1.2.3). tags: | type=raw,value=latest,enable={{is_default_branch}} type=ref,event=branch type=sha,format=short - type=semver,pattern={{version}},event=release - type=semver,pattern={{major}}.{{minor}},event=release + type=semver,pattern={{version}},prefix=v,event=release + type=semver,pattern={{major}}.{{minor}},prefix=v,event=release - name: Build and push - uses: docker/build-push-action@v6 + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 with: context: . file: deploy/sandbox/Dockerfile diff --git a/deploy/sandbox/Dockerfile b/deploy/sandbox/Dockerfile index 58115940..87a4fcdd 100644 --- a/deploy/sandbox/Dockerfile +++ b/deploy/sandbox/Dockerfile @@ -18,21 +18,30 @@ # image builder injects the (static musl) guest agent + init itself when it # converts this OCI image to an ext4 rootfs. # -# Build (multi-arch capable; workdir runs x86_64 Firecracker): +# Build (linux/amd64 only — workdir runs x86_64 Firecracker, and the pinned +# SHA256 checksums below are amd64-specific): # docker buildx build --platform linux/amd64 -f deploy/sandbox/Dockerfile -t kit-sandbox . # -FROM ubuntu:24.04 +FROM --platform=linux/amd64 ubuntu:24.04 ENV DEBIAN_FRONTEND=noninteractive -# Pinned tool versions — override at build time with --build-arg. +# Pinned tool versions — override at build time with --build-arg. Each download +# is verified against the matching SHA256 so a tampered mirror/asset fails the +# build instead of shipping. When bumping a version, update its *_SHA256 too +# (the upstream checksums: go.dev/dl JSON, gh__checksums.txt, +# glab checksums.txt, tea--linux-amd64.sha256). ARG GO_VERSION=1.26.4 +ARG GO_SHA256=1153d3d50e0ac764b447adfe05c2bcf08e889d42a02e0fe0259bd47f6733ad7f ARG KIT_VERSION=latest ARG GH_VERSION=2.95.0 +ARG GH_SHA256=25d1e4729e8808c9ed3d613e96ebd3f3e44446f2d368c89d878a71a36ddb3d8c ARG GLAB_VERSION=1.105.0 +ARG GLAB_SHA256=d8c92c640d7adf84c9dd01d1621fca45471fb61454f025c2fa3046dfc52d0d2f ARG TEA_VERSION=0.14.1 +ARG TEA_SHA256=3cf7c5d1c20808c9ba2efb9ac125cee10d969daf398e653ea2b33cde201ea317 -# Resolved automatically by buildx (linux/amd64 -> amd64, linux/arm64 -> arm64). +# amd64 only (checksums above are amd64; FROM pins the platform). ARG TARGETARCH=amd64 # Base userland — mirrors workdir's curated base image apt layer, plus @@ -46,6 +55,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ # Go toolchain -> /usr/local/go, on PATH for all later layers and at runtime. RUN curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-${TARGETARCH}.tar.gz" \ -o /tmp/go.tgz \ + && echo "${GO_SHA256} /tmp/go.tgz" | sha256sum -c - \ && tar -C /usr/local -xzf /tmp/go.tgz \ && rm /tmp/go.tgz ENV PATH=/usr/local/go/bin:/usr/local/bin:/root/go/bin:$PATH @@ -58,11 +68,12 @@ ENV GOTOOLCHAIN=auto RUN GOBIN=/usr/local/bin go install \ -ldflags "-s -w -X main.version=${KIT_VERSION}" \ "github.com/mark3labs/kit/cmd/kit@${KIT_VERSION}" \ - && kit --version || true + && kit --version # GitHub CLI (gh). RUN curl -fsSL "https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_${TARGETARCH}.tar.gz" \ -o /tmp/gh.tgz \ + && echo "${GH_SHA256} /tmp/gh.tgz" | sha256sum -c - \ && tar -C /tmp -xzf /tmp/gh.tgz \ && install -m755 "/tmp/gh_${GH_VERSION}_linux_${TARGETARCH}/bin/gh" /usr/local/bin/gh \ && rm -rf /tmp/gh.tgz "/tmp/gh_${GH_VERSION}_linux_${TARGETARCH}" @@ -70,6 +81,7 @@ RUN curl -fsSL "https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_$ # GitLab CLI (glab). RUN curl -fsSL "https://gitlab.com/gitlab-org/cli/-/releases/v${GLAB_VERSION}/downloads/glab_${GLAB_VERSION}_linux_${TARGETARCH}.tar.gz" \ -o /tmp/glab.tgz \ + && echo "${GLAB_SHA256} /tmp/glab.tgz" | sha256sum -c - \ && mkdir -p /tmp/glab \ && tar -C /tmp/glab -xzf /tmp/glab.tgz \ && install -m755 /tmp/glab/bin/glab /usr/local/bin/glab \ @@ -77,8 +89,10 @@ RUN curl -fsSL "https://gitlab.com/gitlab-org/cli/-/releases/v${GLAB_VERSION}/do # Gitea CLI (tea) — single static binary. RUN curl -fsSL "https://dl.gitea.com/tea/${TEA_VERSION}/tea-${TEA_VERSION}-linux-${TARGETARCH}" \ - -o /usr/local/bin/tea \ - && chmod +x /usr/local/bin/tea + -o /tmp/tea \ + && echo "${TEA_SHA256} /tmp/tea" | sha256sum -c - \ + && install -m755 /tmp/tea /usr/local/bin/tea \ + && rm -f /tmp/tea # Kit's OAuth callback listener binds localhost; without this entry it fails to # create the listener and nil-panics on cleanup. Bake it in so the runtime @@ -89,10 +103,11 @@ RUN grep -q localhost /etc/hosts || echo '127.0.0.1 localhost' >> /etc/hosts RUN mkdir -p /workspace WORKDIR /workspace -# Smoke-check that every CLI resolves on PATH at build time. +# Smoke-check that every CLI resolves on PATH at build time. No `|| true`: +# `kit` is the main payload, so a broken binary must fail the build. RUN set -e; \ go version; \ - kit --version || true; \ + kit --version; \ gh --version; \ glab --version; \ tea --version From 05c9413bffde70203599f449ebcc1555a025f393 Mon Sep 17 00:00:00 2001 From: ezynda3 Date: Fri, 26 Jun 2026 17:18:59 +0300 Subject: [PATCH 3/3] docs(sandbox): pin immutable image tag in workdir registration example Use ghcr.io/mark3labs/kit-sandbox:v0.82.1 (an immutable release tag) instead of :latest in the POST /v1/images example, with a note to prefer vX.Y.Z or sha- over latest, so the workdir image definition is reproducible. --- deploy/sandbox/README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/deploy/sandbox/README.md b/deploy/sandbox/README.md index a60677d2..e81e3cff 100644 --- a/deploy/sandbox/README.md +++ b/deploy/sandbox/README.md @@ -55,7 +55,9 @@ Tags published: `latest` (default branch), `sha-`, branch name, and ## Register it as a workdir custom image Once published, register the OCI image with workdir -([API reference](https://github.com/mv37-org/workdir/blob/main/docs/API.md#images-spec-10-11)): +([API reference](https://github.com/mv37-org/workdir/blob/main/docs/API.md#images-spec-10-11)). +Pin an **immutable** tag (a release `vX.Y.Z` or a `sha-` tag) rather than +`latest`, so the workdir image definition is reproducible: ```bash curl -fsSL -X POST https://api.workdir.dev/v1/images \ @@ -63,7 +65,7 @@ curl -fsSL -X POST https://api.workdir.dev/v1/images \ -H "Content-Type: application/json" \ -d '{ "source": { "type": "oci", - "image_ref": "ghcr.io/mark3labs/kit-sandbox:latest" }, + "image_ref": "ghcr.io/mark3labs/kit-sandbox:v0.82.1" }, "name": "custom/mark3labs/kit-sandbox", "resources_hint": { "cpu": 2, "memory_mb": 4096, "disk_gb": 16 } }'