Image builder
The image builder turns a declarative description of an environment into a real
container image. Instead of hand-writing Dockerfiles, you compose an Image from
plugins; each plugin updates a shared build context and emits a Dockerfile fragment.
The result is compiled to an ImageBuildSpec and built — locally with Docker, or
in-cluster through a pluggable build backend (Kaniko by default, or GKE Cloud Build).
The build pipeline (click a node for source)
How it works
Image is an immutable description: a base, an ordered list of plugins, optional trailing
shell commands, and runtime config (Kubernetes runtime class + resources). Every fluent
method returns a new Image.
The base is given either as an image reference (base) or as Dockerfile text
compiled in the same build (base_dockerfile) — see
Basing on an inline Dockerfile.
When you call image.to_spec(), the builder:
- Normalizes
base_dockerfile, if one was given: aliases the stage that acts as the base and hoists parser directives. - Creates a
BuildContextwhosebaseis that alias (or the image reference), with defaults (current_user="root",home="/root",project_root="/root/work"). - Walks the plugins in order, interleaving for each one:
apply(ctx)→ updates the context (e.g. theuserplugin setscurrent_user), thenrender(ctx)→ returns a Dockerfile fragment. - Assembles the final Dockerfile (hoisted directives, the user's own stages, plugin build
stages,
FROM, download ARGs,ENVs, all fragments, the finalUSER, the commands block) and any MCP-upstream config files into anImageBuildSpec.
Plugin order matters. Because
apply()andrender()are interleaved, a plugin'srender()only sees context set by itself and earlier plugins. Putuserbefore the plugins that should run as that user.
from idegym.image.builder import Image
from idegym.plugins.defaults.image import BaseSystem, User, Project, IdeGYMServer
image = (
Image.from_base("debian:bookworm-slim")
.with_plugin(BaseSystem()) # apt packages
.with_plugin(User(username="appuser", uid=1000, gid=1000))
.with_plugin(IdeGYMServer.from_git(url=..., ref="main"))
.with_plugin(Project.from_git(url=..., ref="abc123", owner="appuser"))
)
spec = image.to_spec() # → ImageBuildSpec (inspect spec.dockerfile_content)
Basing on an inline Dockerfile
A custom base used to mean a separate build-and-push cycle before the real build: registry
write credentials for every environment author, a second async build the orchestrator did not
track, and an intermediate image with no lifecycle owner. base_dockerfile removes that round
trip by compiling the base into the same build.
The merge emits your text verbatim apart from one edit: the stage acting as the base gains an
AS idegym_base alias, and only if it does not already declare one — renaming your stage would
break your own COPY --from= references. FROM idegym_base then inherits that stage's full image
config (ENV, WORKDIR, USER, ENTRYPOINT, CMD), which is what makes this equivalent to
publishing the base and referencing it by tag.
Inputs that cannot work are rejected before a build is submitted — a missing FROM, an unknown
base_stage, a stage in the reserved idegym_ namespace, BuildKit-only syntax on Kaniko — rather
than surfacing minutes later in a build log.
One thing inheritance does not cover: ENTRYPOINT, CMD and HEALTHCHECK. Plugin fragments
render after the primary FROM, and idegym-server declares all three because it owns how the
container starts. A base whose ENTRYPOINT performs the real setup therefore loses it silently, so
compiling such a definition records a warning on the job. Move that setup into a build-time RUN,
or into /docker-entrypoint.d/ — see the
reference.
Build context by reference
An inline Dockerfile that COPYs from a build context needs one. context_uri names an archive the
caller has already staged (gs://…), so the orchestrator never receives context bytes over the
API. Kaniko fetches it natively as its --context; Cloud Build overlays it into the workspace with
a fetch step, deliberately not clobbering the generated Dockerfile or plugin assets.
Deduplication still holds. The tag is a pure function of the definition, so two identical submissions produce the same tag and the second is a tag hit rather than a rebuild. Because the tag derives from the context URI rather than its bytes, callers must name context objects by content. See build cost and deduplication.
Built-in image plugins
| Type | Class | Role |
|---|---|---|
base-system | BaseSystem | Install apt packages (or a minimal set) |
user | User | Create a Linux user/group; updates ctx.current_user |
permissions | Permissions | chown / chmod paths |
project | Project | Load a project: git, resource, local COPY, archive, or git-clone |
mcp-upstream | MCPUpstream | Declare an in-pod MCP server URL |
idegym-server | IdeGYMServer | Install the IdeGYM server runtime (from_local / from_git) |
pycharm / idea | PyCharm / Idea | Install a JetBrains IDE (optional plugin extras) |
Plugins are discovered via the idegym.plugins.image entry-point group at import time,
so YAML deserialization always sees a populated registry. See
plugins for how to write your own.
Building: local vs. in-cluster
- Local Docker —
image.build()uses the local Docker daemon; handy for development and loading into Minikube. Defaults to a native single-platform build (your host's architecture); pass--multiplatformonly when you deliberately wantlinux/amd64+linux/arm64. - In-cluster — serialize to YAML and submit to the orchestrator, which runs the build through a pluggable backend (below) and pushes the result to the configured registry.
Shipping files a plugin COPYs
A plugin whose render() emits a COPY needs its source to exist in the build context.
Rather than assume the caller builds from a checkout of the idegym repo, a plugin ships the
file inside its own package and declares it from get_context_files() (dest → packaged file); to_spec() folds these onto ImageBuildSpec.context_files and into the image tag,
so an asset change yields a new tag. The local Docker build stages any missing declared
files in place into the caller's context (cleaning up only what it created), so COPY
resolves from any working directory. See Plugin system and the
reference.
Build backends
The in-cluster path is backend-agnostic: the orchestrator picks an ImageBuilder at
request time and a shared ImageBuildService drives it — build the tag, persist status to
the job_statuses table, and poll to completion. The backend is selected via config
(IDEGYM_BUILD_BACKEND), defaulting to kaniko so existing deployments are unchanged.
- Kaniko (default) — an in-cluster Job that builds from
dockerfile_content(mounted as a ConfigMap) and pushes to the registry; download ARGs (project URL, auth token) are passed as--build-argvalues. Kaniko accepts a single--context, which is why a caller-suppliedcontext_uriand the git checkout that resolves plugincontext_filescannot be combined here. When a spec carriescontext_filesand nocontext_uri, the Job's context is a git checkout of the idegym repo at the orchestrator's version, so thoseCOPYpaths resolve. - GKE Cloud Build — builds with GCP Cloud Build
(BuildKit) instead of an in-cluster Job, pushing to Artifact Registry. The project auth
token is passed as a BuildKit build secret (never a
--build-arg), so it never lands in the Build request, its logs, or the image history. Plugincontext_filestravel in the uploaded context tar, and a callercontext_uriis overlaid by a preceding fetch step, so both can coexist.
Capability differences are validated up front and reported as errors, never discovered from a
build log. The ones that matter: BuildKit-only Dockerfile syntax (heredocs, RUN --mount) is Cloud
Build only; machine_type / disk_size_gb are Cloud Build only, since a Kaniko build is sized by
the per-image resources field; and secrets become real secret mounts on Cloud Build but
--build-arg values on Kaniko, which exposes them in the image history.
Kaniko +
set -ugotcha: ARGs without a--build-argvalue are unset (not empty). IdeGYM'sRUN set -eux;usesset -u, so optional ARGs are referenced as${VAR:-}. The built-in plugins handle this for you.
→ Full backend configuration (env vars, GCP IAM), the complete capability matrix, and the secret-handling caveats are in the reference — build backends.
View source
- Fluent API →
image-builder/src/idegym/image/builder.py - Built-in plugins →
plugins/defaults/src/idegym/plugins/defaults/image.py - Spec model →
api/src/idegym/api/image_build.py - Build backends →
backend-utils/src/idegym/backend/utils/image_builder/ - Full reference → Image Builder docs