Build a workspace image#

workspace image demo

conda workspace image builds one locked Linux environment and the workspace’s project files into a runnable container image. It delegates image construction to Docker Buildx and installs packages through conda inside the Linux builder.

Install Docker with Buildx and ensure its daemon is running. Image tooling is optional for other workspace commands and for image --dry-run.

Prepare the environment#

Set the workspace’s name, declare a runtime environment and Linux platform in the manifest, then generate conda.lock. The image command requires a current, complete lockfile and never updates the source manifest or lockfile. The workspace name must be a nonempty portable directory name without dollar signs or apostrophes. Image environment paths have the same restriction. Conda expands environment variables when activating a prefix, and Dockerfile parsing interprets apostrophes in source paths. Spaces are supported.

Applications can run directly from the copied project files, for example with python -m myapp or python app.py. Declare their runtime requirements in the selected environment. Version-based PyPI dependencies use the existing conda-pypi integration. Python path, Git, and URL dependencies are not supported by the image command.

Local Python package builds are tracked in #178. They will use the shared workspace installer once conda-pypi provides strict builds with locked requirements and activated backends through conda-pypi#521 and conda-pypi#523.

The container example includes application source files, activation settings, and a lockfile for both supported Linux architectures. Build it with the image command in its README.

Build and run#

conda workspace image -e runtime --platform linux-64 \
  -t myapp:latest --load -- python -m myapp

docker run --rm myapp:latest

Use linux-aarch64 for ARM64. A named workspace platform that maps to either supported subdir is also accepted. Building for another architecture requires a builder capable of running that architecture, either natively or with emulation.

The command after -- becomes the image’s default command. Docker command arguments override it while preserving activation:

docker run --rm myapp:latest python -c 'import sys; print(sys.prefix)'

The final image contains selected project files at /workspaces/<name>, using the manifest’s workspace name. This is also its working directory. The selected environment stays at /workspaces/<name>/.conda/envs/<environment>. Its bin directory and /workspaces/<name>/.conda/bin are on PATH. The workspace’s workspace-entrypoint applies conda environment variables and sources activation hooks before using exec to start the application. Startup performs no installation or solving. Conda and temporary build tools are excluded unless the selected environment or base image itself includes them.

Mount runtime data into a subdirectory, for example --mount type=bind,src=/path/to/data,dst=/workspaces/myapp/data for a workspace named myapp. Mounting over /workspaces/myapp would hide both its project files and installed environment.

Choose a compatible glibc-based Linux base with /bin/bash:

conda workspace image -e runtime --platform linux-64 \
  --base-image ubuntu:24.04 -t myapp:latest --load -- python -m myapp

The default is debian:bookworm-slim. The build checks native virtual packages against locked package and manifest requirements. The runtime host must also meet kernel, CPU, and driver requirements. Pin base images by digest when a stable base is required. The bootstrap image is versioned. It installs native Linux tool dependencies separately from the locked application. The build uses a copy of the invoking conda-workspaces Python sources and a released conda-pypi package.

The generated recipe uses the ordinary install command for both phases, equivalent to:

RUN /opt/conda/bin/python -m conda workspace install \
    --locked -e runtime --platform linux-64 --download-only
RUN --network=none /opt/conda/bin/python -m conda workspace install \
    --locked -e runtime --platform linux-64

The first phase validates the lockfile and fetches its packages without creating an environment. The second installs the locked environment with networking disabled. The generated commands use the selected manifest, environment, and platform. conda_workspaces.image_entrypoint then renders the runtime activation wrapper.

The base must not already contain the selected workspace path, such as /workspaces/myapp. Other directories under /workspaces are allowed. This prevents files from an earlier version of the selected workspace or environment surviving a new build. Extend an existing workspace image with a downstream Dockerfile.

Each invocation builds one workspace and selects one default runtime command. Named workspace directories can coexist, each keeping its own manifest, environment, and activation wrapper. Their task and dependency graphs remain separate.

Extend the image#

Use the generated image in FROM to add files and build steps:

FROM myapp:latest
WORKDIR /srv/app
COPY app.py .
RUN workspace-entrypoint python -m py_compile app.py
CMD ["python", "app.py"]

Ordinary commands such as RUN python --version find the selected environment through PATH. Docker build steps do not use the image’s ENTRYPOINT. Invoke workspace-entrypoint explicitly when a build step needs manifest environment variables or activation hooks. The derived image retains the activating entrypoint for its runtime command. Changing WORKDIR changes the command’s working directory without relocating the installed environment.

For a multistage build using COPY --from, preserve the installed prefix’s absolute path and use a compatible Linux base. Copying /workspaces/<name> to the same location includes the environment, project files, and its activation wrapper at /workspaces/<name>/.conda/bin/workspace-entrypoint. Copying files does not carry image configuration such as PATH, ENTRYPOINT, CMD, or WORKDIR. Set those in the final stage as needed.

Export or publish#

Choose exactly one destination. --load loads an image into the local Docker image store. --push publishes tagged images through the builder’s registry support and existing authentication:

conda workspace image -e runtime --platform linux-64 \
  -t registry.example.org/myapp:latest --push -- python -m myapp

-o/--output writes a standard OCI image layout tar archive:

conda workspace image -e runtime --platform linux-64 \
  -t myapp:latest -o myapp.oci.tar -- python -m myapp

The archive contains oci-layout, index.json, and content-addressed blobs. Use an OCI-aware tool to copy or import it, for example:

skopeo copy oci-archive:myapp.oci.tar docker-daemon:myapp:latest

Use --load when loading directly into Docker, whose archive import support varies by image store. Existing output files are never overwritten. A failed build does not publish a partial archive.

By default the command creates a temporary docker-container Buildx builder and removes it afterward. For repeated builds, reuse a builder and its cache:

docker buildx create --name workspace-images --driver docker-container
conda workspace image -e runtime --platform linux-64 \
  --builder workspace-images -t myapp:latest --load -- python -m myapp

An explicitly selected builder remains available after the command. OCI archive exports require a supporting driver such as docker-container. See Docker’s OCI exporter documentation.

Inspect the inputs#

conda workspace image -e runtime --platform linux-64 \
  -t myapp:latest --load --dry-run --json -- python -m myapp

The preview includes the recipe, build package names, selected files, environment, platform, base, tags, and destination. Successful builds return these artifact identifiers in JSON: environment, workspace, prefix, platform, oci_platform, tags, output, load, push, digest, and image_id. Build logs go to stderr.

Project files follow the existing archive selection rules. Git workspaces include tracked files, plus the manifest and lockfile when allowed by the archive filters. Required activation scripts must be included. Ensure the selected files include the application modules used by the runtime command.

Python path, Git, and URL dependencies, editable installs, and local conda channels are rejected. Container configuration schemas, multi-architecture image indexes, and task-oriented entrypoints are not supported.