---
title: The Construct Image
description: What's inside the shared base image that every agent starts from
---
import RepoFile from '../../../components/RepoFile.astro'
import { Aside } from '@astrojs/starlight/components'

<Aside type="note">
This page has two readers. **Role authors** (user-facing) read the
top half — *what is in the `construct`*, *what tools come for free*,
*how to extend it from a role's `Dockerfile`*. **Contributors**
(internals-facing) read the lower half — *how to build, validate,
and publish the `construct` image itself* (`just construct-build`,
CI workflow, advanced publish rehearsal). Operators using jackin'
as a product do not need to read either half; `jackin load` pulls
and updates the `construct` automatically.
</Aside>

## What is the construct?

`projectjackin/construct:trixie` is the shared base Docker image for every agent — the foundation layer that every role extends. It provides system tools, shell environment, and container infrastructure, kept in one image so every agent inherits the same baseline.

Every agent Dockerfile starts from the construct:

```dockerfile
FROM projectjackin/construct:0.1-trixie
```

## What's inside

The construct is built on **Debian Trixie** and includes:

### System tools

| Package | Purpose |
|---|---|
| `bash` | Default shell for scripts |
| `zsh` + Oh My Zsh | Interactive shell with autosuggestions |
| `git` + Git LFS | Source control with large file support |
| `curl` | HTTP client |
| `jq` + `yq` | JSON and YAML processors |
| `openssh-client` | SSH for git operations |
| `sudo` | Privilege escalation (passwordless for agent user) |
| `tree` | Directory visualization |

### Search tools

| Package | Purpose |
|---|---|
| `ripgrep` (`rg`) | Fast regex search |
| `fd-find` (`fd`) | Fast file finder |
| `fzf` | Fuzzy finder for interactive selection |

### Development infrastructure

| Package | Purpose |
|---|---|
| `mise` | Polyglot language version manager |
| Docker CLI + Compose | Container operations via DinD |
| GitHub CLI (`gh`) | Repository, PR, and issue operations |
| Starship prompt | Informative terminal prompt |

### User environment

The construct creates an `agent` user with:
- Home directory at `/home/agent`
- Zsh as the default shell
- Passwordless sudo access
- Oh My Zsh with autosuggestions plugin
- Starship prompt configured
- mise shims in `$PATH`

## How it's built

The construct source starts at <RepoFile path="docker/construct/Dockerfile" /> in the jackin' repository. The declarative build definition lives in <RepoFile path="docker-bake.hcl" /> at the repo root, and the supported command wrapper is <RepoFile path="Justfile" /> at the repo root.

<RepoFile path="docker/construct/versions.env" /> remains the source of truth for the pinned `tirith` and `shellfirm` build args used by <RepoFile path="docker/construct/Dockerfile" />.

## Build workflow

Use `just` for day-to-day construct work. Docker Bake is the underlying build engine, but contributors should treat `just` as the supported command surface.

### Local validation

Before opening a pull request for construct changes, validate the image locally:

```bash
just construct-init-buildx
just construct-build-local
```

By default, local validation loads the image into your Docker daemon as `jackin-local/construct:trixie`, so it does not silently replace the canonical `projectjackin/construct:trixie` base image that normal `jackin` workflows consume.

To test `jackin load` against your local construct build, set `JACKIN_CONSTRUCT_IMAGE` after building:

```bash
just construct-init-buildx
just construct-build-local
export JACKIN_CONSTRUCT_IMAGE="jackin-local/construct:trixie"
```

With `JACKIN_CONSTRUCT_IMAGE` set, jackin validates role Dockerfiles against the canonical image name as usual (role repos reference a versioned construct tag such as `projectjackin/construct:0.1-trixie`), but substitutes your local image at derived-build time so `docker build` uses `jackin-local/construct:trixie` as the actual base.

Use `just construct-inspect` to print the fully resolved Bake config without building the image.

If builder state gets stale or confusing, run `just construct-doctor-buildx` to inspect it or `just construct-reset-buildx` to recreate it. Set `BUILDX_BUILDER` before running `just` if you want to isolate this workflow under a different local builder name.

### Platform-specific debugging

To force a specific target architecture during local debugging:

```bash
just construct-build-platform amd64
just construct-build-platform arm64
```

These commands work when your buildx builder supports the target platform natively or through emulation.

### Advanced publish rehearsal

If you need to rehearse the release path against a temporary registry, point `REGISTRY_IMAGE` at your own namespace instead of the canonical `projectjackin/construct` repository:

```bash
REGISTRY_IMAGE=ttl.sh/jackin-construct-$USER just construct-push-platform amd64
REGISTRY_IMAGE=ttl.sh/jackin-construct-$USER just construct-push-platform arm64
REGISTRY_IMAGE=ttl.sh/jackin-construct-$USER just construct-publish-manifest
```

<Aside type="caution">
`just construct-push-platform` and `just construct-publish-manifest` intentionally refuse to publish to `projectjackin/construct` unless they are running in CI.
</Aside>
### CI behavior

GitHub Actions reuses the same `just` commands on native `ubuntu-24.04` and `ubuntu-24.04-arm` runners. Construct CI triggers when changes touch `docker/construct/**`, <RepoFile path="docker-bake.hcl" />, <RepoFile path="Justfile" />, or <RepoFile path=".github/workflows/construct.yml" />.

Pull requests, plus manual workflow runs against non-`main` branches, build both architectures natively without pushing images. Those validation jobs use the GitHub Actions cache backend so cache state stays inside Actions instead of being published to Docker Hub.

Pushes to `main`, plus manual workflow runs against `main`, publish each platform by digest first, then assemble the final multi-platform manifest from those digests. That keeps the public tag surface limited to the canonical release tags instead of leaving permanent public `-amd64` and `-arm64` tags behind.

Published images also carry explicit BuildKit provenance and SBOM attestations.

The published tags are:
- `projectjackin/construct:trixie` — the stable tag
- `projectjackin/construct:trixie-<sha>` — commit-specific tag

## The derived image layer

When you load an agent, jackin' doesn't use the construct directly. It generates a **derived Dockerfile** that adds:

1. **User remapping** — adjusts the `agent` user's UID/GID to match your host user
2. **Agent installation** — installs every agent declared in the role manifest
3. **Plugin installation** — any Claude plugins declared in the role manifest
4. **Runtime entrypoint** — the script that configures git, wires up GitHub if `gh` is already authenticated (it does not perform `gh auth login` — an unauthenticated `gh` is left alone with a notice), dispatches agent setup, and launches the selected agent

<Aside type="note">
Every load re-runs `docker build` on the derived Dockerfile. Docker's layer cache makes most subsequent loads fast, but there is no separate "skip build if image exists" shortcut. Passing `--rebuild` additionally invalidates agent installation layers so the build picks up newer agent CLI downloads.
</Aside>
## Extending the construct

Role repos add their tools on top of the construct. The construct provides the foundation — agents provide the specialization:

```
┌─────────────────────────────────┐
│  Derived Layer (jackin-managed) │  Agent CLIs, entrypoint, user mapping
├─────────────────────────────────┤
│  Agent Layer (your Dockerfile)  │  Rust, Node, Python, custom tools
├─────────────────────────────────┤
│  Construct (shared base)        │  Debian, git, Docker CLI, mise, zsh
└─────────────────────────────────┘
```

This layered approach means:
- Agent authors focus on their tools, not infrastructure
- The construct can be updated independently (security patches, new tools)
- Docker layer caching makes builds fast
