-
Notifications
You must be signed in to change notification settings - Fork 14
feat(sandbox): official Kit sandbox image + GHCR workflow #78
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| 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: | ||
| # 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 [ "$EVENT_NAME" = "release" ]; then | ||
| version="$RELEASE_TAG" | ||
| elif [ -n "$INPUT_KIT_VERSION" ]; then | ||
| version="$INPUT_KIT_VERSION" | ||
| else | ||
| 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@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 | ||
|
|
||
| - name: Log in to GHCR | ||
| uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 | ||
| with: | ||
| registry: ${{ env.REGISTRY }} | ||
| username: ${{ github.actor }} | ||
| password: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| - name: Docker metadata (tags + labels) | ||
| id: meta | ||
| 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}},prefix=v,event=release | ||
| type=semver,pattern={{major}}.{{minor}},prefix=v,event=release | ||
|
|
||
| - name: Build and push | ||
| uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # 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 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,115 @@ | ||
| # 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 (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 --platform=linux/amd64 ubuntu:24.04 | ||
|
|
||
| ENV DEBIAN_FRONTEND=noninteractive | ||
|
|
||
| # 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_<v>_checksums.txt, | ||
| # glab checksums.txt, tea-<v>-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 | ||
|
|
||
| # amd64 only (checksums above are amd64; FROM pins the platform). | ||
| 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 \ | ||
| && 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 | ||
| 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 | ||
|
|
||
| # 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}" | ||
|
|
||
| # 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 \ | ||
| && 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 /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 | ||
| # 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. No `|| true`: | ||
| # `kit` is the main payload, so a broken binary must fail the build. | ||
| RUN set -e; \ | ||
| go version; \ | ||
| kit --version; \ | ||
| gh --version; \ | ||
| glab --version; \ | ||
| tea --version | ||
|
|
||
| CMD ["/bin/bash"] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| # 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-<short>`, 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)). | ||
| Pin an **immutable** tag (a release `vX.Y.Z` or a `sha-<short>` tag) rather than | ||
| `latest`, so the workdir image definition is reproducible: | ||
|
|
||
| ```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:v0.82.1" }, | ||
| "name": "custom/mark3labs/kit-sandbox", | ||
| "resources_hint": { "cpu": 2, "memory_mb": 4096, "disk_gb": 16 } | ||
| }' | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| ``` | ||
|
|
||
| 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). | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.