# Building Container Images

> Cordium documentation. Canonical page: <https://octelium.com/docs/cordium/latest/examples/automation/image-build>.

*Workspaces* can build and push OCI container images with rootless Podman, without a Docker daemon and without any privileged access to the host (read more [here](https://octelium.com/docs/cordium/latest/workspaces/runtime.md#nested-containers)). This example uses an ephemeral, auto-stopping *Workspace* to build an image from a repository's Dockerfile, push it to GitHub Container Registry, and print its digest.

## The Template

The registry token is a *Secret* of the `payments` *Space* with the `write:packages` scope. The image, tag and Dockerfile are variables, so that the same *Template* can build any image of the *Space*'s repositories:

```bash
cordium create secret ghcr-push-token.payments.cordium --from-env GHCR_TOKEN
```

```yaml
spec:
  vars:
    - name: REPO
      value: acme-corp/payments-api
    - name: REF
      value: main
    - name: IMAGE
      value: ghcr.io/acme-corp/payments-api
    - name: TAG
      value: latest
    - name: DOCKERFILE
      value: Dockerfile
  repository:
    url: https://github.com/${{vars.REPO}}
    cloneOptions:
      depth: 1
      singleBranch: true
      disableLazyUnshallow: true
    authentication:
      http:
        username: x-access-token
        password:
          fromSecret: github-read-token.payments.cordium
  runtime:
    autoStop: true
    envVars:
      - key: REGISTRY_USERNAME
        value: acme-bot
      - key: REGISTRY_TOKEN
        fromSecret: ghcr-push-token.payments.cordium
    tasks:
      - name: install-podman
        type: ON_CREATE
        runAsRoot: true
        onFailure: ON_FAILURE_ABORT
        run: |
          apt-get update
          apt-get install -y --no-install-recommends podman skopeo
      - name: build-and-push
        type: ON_CREATE
        workingDir: /workspace/repo
        onFailure: ON_FAILURE_ABORT
        run: |
          set -e
          git fetch -q --depth 1 origin "${{ vars.REF }}"
          git checkout -q FETCH_HEAD
          printf '%s' "$REGISTRY_TOKEN" | sudo podman login ghcr.io -u "$REGISTRY_USERNAME" --password-stdin
          sudo podman build --pull=always \
            --file "${{ vars.DOCKERFILE }}" \
            --label "org.opencontainers.image.revision=$(git rev-parse HEAD)" \
            --tag "${{ vars.IMAGE }}:${{ vars.TAG }}" .
          sudo podman push --digestfile /tmp/digest "${{ vars.IMAGE }}:${{ vars.TAG }}"
          echo "Pushed ${{ vars.IMAGE }}@$(cat /tmp/digest)"
  limit:
    cpu:
      millicores: 8000
    memory:
      megabytes: 16384
    storage:
      megabytes: 40000
```

Here are a few notes about this *Template*:

- `sudo` does not preserve the environment, which is why the token is piped to `podman login` rather than read by it from `$REGISTRY_TOKEN`.
- `REF` can be a branch, a tag or a commit, which is fetched and checked out before the build. The repository URL is written as `${{vars.REPO}}`, without spaces, since URLs are validated before the variables are substituted (read more [here](https://octelium.com/docs/cordium/latest/workspaces/overview.md#variables)).
- The image is built from the repository's own Dockerfile and context. Image builds use the *Workspace*'s CPU, memory and storage, so size the limits for your largest image.
- The run fails if any step fails, and the pushed digest appears in the *Workspace*'s logs.

```bash
cordium create template image-build.payments.cordium --file image-build.yaml
cordium create ws --template image-build.payments.cordium --ephemeral --start \
  --var REF=v2.19.0 --var TAG=2.19.0
```

> **Note:**
>
> Since `REGISTRY_TOKEN` is an environment variable, it is readable by every process running in the *Workspace*, including the build's `RUN` steps of an untrusted Dockerfile. Only build images from repositories that you trust with this token, use a token that can only push to the intended packages, and prefer short-lived tokens where your registry supports them.

## Using the Image in Workspaces

Images pushed this way can be used as the image of other *Templates* and *Workspaces*, which is an alternative to pre-builds for heavy toolchains that change rarely (read more [here](https://octelium.com/docs/cordium/latest/workspaces/image.md#registry)):

```yaml
spec:
  image:
    registry:
      url: ghcr.io/acme-corp/payments-dev:2026.10
      authentication:
        username: acme-bot
        password:
          fromSecret: ghcr-read-token.payments.cordium
```
