lc build¶
Build container images declared in astra.yaml (or pre-pull registry
images so lc run can use --pull=never).
Synopsis¶
Options¶
| Option | Default | Effect |
|---|---|---|
--force |
off | Rebuild / re-pull even if the tag already exists locally. |
--runtime {docker,podman,podman-hpc,kubernetes} |
resolved from ~/.lightcone/config.yaml |
Override the runtime for this build. |
What it does¶
For every distinct container: value found in the project (root,
sub-analysis, or recipe-level):
- Path to a Containerfile → compute the content-addressed tag
lc-<project>-<sha256[:12]>, build the image, and (forpodman-hpc) migrate it into the per-node container cache. - Anything else (e.g.
python:3.12-slim,ghcr.io/foo/bar:tag) → pull it into the local image store. This is what letslc runpass--pull=neverto the runtime, sidesteppingunqualified-search-registriesresolution issues with content-addressed tags.
On the kubernetes runtime (a lightcone JupyterHub deployment, where
no local OCI runtime exists) the same command builds through the
deployment's GCP Cloud Build service instead: the staged build
context is uploaded to the deployment's build bucket and the resulting
image is pushed as $LIGHTCONE_REGISTRY/lc-<project>:<sha256[:12]> —
the same content-addressed identity, so an unchanged environment is a
single registry check and no build at all. Pre-built registry images
are left alone (worker pods pull them directly). Auth is the pod's
Workload Identity; nothing to configure.
If the runtime is none (either by config or because auto couldn't
find one), lc build prints a friendly note and exits 0. There is
nothing to build.
Tag computation¶
(On the kubernetes runtime the same identity is spelled
<registry>/lc-<sanitized-project-name>:<sha256[:12]> — the digest is
identical, it just moves into the tag position.)
The hash covers three things:
- The Containerfile contents.
- Any of these dependency files at the project root:
requirements.txt,requirements-dev.txt,requirements-test.txt,pyproject.toml,setup.py,setup.cfg,poetry.lock,Pipfile.lock,uv.lock,conda-lock.yml,environment.yml,environment.yaml. - Every
COPY/ADDsource referenced from the Containerfile: files are hashed directly, directories are walked recursively.COPY .therefore covers the whole project tree, minus a fixed exclude list (.git,.venv,venv,__pycache__,results,.lightcone,.snakemake,node_modules, and the usual tool caches) so the tag doesn't churn every time you run the pipeline.
Sources behind --from=<stage>, and URL / git ADD arguments, are
skipped — they aren't part of the host build context.
Edit any of the above and the tag changes. That, in turn, changes
code_version in every recipe that uses the image, which marks all
downstream outputs stale in lc status.
New in v0.4.0
Point 3 is new. Projects whose Containerfile copies source code
(COPY src/ /app/src, COPY . /app) will see one extra rebuild
the first time they run under v0.4.0, and will now correctly
rebuild when that copied code changes — which previously went
unnoticed.
Examples¶
lc build # build / pull whatever's missing
lc build --force # rebuild / re-pull everything
lc build --runtime podman-hpc # force the HPC runtime
lc build --runtime kubernetes # force the Cloud Build path (JupyterHub)
--runtime kubernetes is normally unnecessary: on a lightcone
JupyterHub the site is detected from DASK_GATEWAY__ADDRESS and the
runtime resolves to kubernetes on its own. Pass it explicitly to pin
the behaviour, or to build against the deployment registry from a
session where detection would have picked something else. It requires
the Cloud Build contract in the environment — LIGHTCONE_REGISTRY and
LIGHTCONE_BUILD_BUCKET (plus the optional
LIGHTCONE_BUILD_SERVICE_ACCOUNT) — and a metadata-served GCP identity;
without those lc build errors with "No image build backend on this
host".
Pre-staging for HPC¶
On a login node:
$EDITOR ~/.lightcone/config.yaml # container.runtime: podman-hpc
lc build # builds + migrates everything
Then submit a SLURM job for lc run. The compute nodes will find every
image already cached.
See api/container for the implementation and
Architecture for why we wrap recipes ourselves
instead of using Snakemake's container: directive.