# container

The `container` step type builds, pushes, runs, and inspects containers through Docker or Podman, bringing the full container lifecycle into your custom commands and workflows without leaving Atmos.

Set `action` to choose the operation, then provide the operation's parameters under a single `with:` block. When `action` is omitted, Atmos defaults to `run`.

```yaml
steps:
  - name: build
    type: container
    action: build
    with:
      context: .
      dockerfile: Dockerfile
      tags:
        - app:local

  - name: smoke
    type: container
    action: run
    with:
      image: app:local
      command: uname -a
```

This step type is different from the step-level [`container`](/workflows/steps/container) field, which runs ordinary `type: shell` steps inside a shared sandbox.

## Fields

- **`action`**
  Operation to perform: 
  `build`
  , 
  `push`
  , 
  `run`
  , or 
  `inspect`
  . Defaults to 
  `run`
   when omitted.
- **`with`**
  Parameters for the chosen 
  `action`
  . The accepted fields depend on the action: see 
  [Build](#build)
  , 
  [Push](#push)
  , 
  [Run](#run)
  , and 
  [Inspect](#inspect)
  .
- **`background`**
  For 
  `action: run`
  , start the container detached as a long-running service and continue the workflow. See 
  [Background services](#background-services)
  .

The `provider` (`auto`, `docker`, or `podman`; auto-detected when `auto` or omitted) and `runtime_auto_start` (start the Podman machine when no runtime is running) keys stay at the top level of the step, alongside `action` and `with`.

## Build

`action: build` builds a container image from a Dockerfile, or from a Docker Bake definition.

```yaml
steps:
  - name: build
    type: container
    action: build
    provider: docker            # auto | docker | podman (auto-detected when auto/omitted)
    runtime_auto_start: true    # start the Podman machine if no runtime is running
    with:
      engine: buildx            # buildx (Docker BuildKit) or omit for the default builder
      context: .                # build context directory
      dockerfile: Dockerfile    # path to the Dockerfile
      tags:
        - app:local
        - app:1.0.0
      build_args:               # values passed as --build-arg
        VERSION: 1.0.0
      target: runtime           # target stage in a multi-stage build
      no_cache: false           # disable the build cache
      pull: true                # always pull newer base images
      driver: docker-container  # Buildx builder driver (shorthand — see Driver below)
      cache:                    # Buildx cache import/export (see Cache below)
        from:
          - type: registry
            ref: registry.example.com/app:buildcache
        to:
          - type: registry
            ref: registry.example.com/app:buildcache
            mode: max
```

- **`context`**
  Build context directory. Required unless you use 
  `bake`
  .
- **`dockerfile`**
  Path to the Dockerfile. Required unless you use 
  `bake`
  .
- **`tags`**
  List of image tags to apply.
- **`build_args`**
  Map of build arguments passed as 
  `--build-arg`
  .
- **`target`**
  Target stage in a multi-stage Dockerfile.
- **`no_cache`**
  Disable the build cache when set to 
  `true`
  .
- **`pull`**
  Always attempt to pull newer base images when set to 
  `true`
  .
- **`engine`**
  Build engine: 
  `buildx`
   for Docker BuildKit, or omit for the default builder. Requires 
  `provider: docker`
  .
- **`bake`**
  Build with Docker Bake instead of a single Dockerfile. See 
  [Bake](#bake)
  . Requires 
  `provider: docker`
  .
- **`driver`**
  Buildx builder instance to build (or bake) with. See 
  [Driver](#driver)
  . Requires 
  `provider: docker`
   and 
  `engine: buildx`
  .
- **`cache`**
  Buildx cache import/export sources for the plain (non-bake) build path. See 
  [Cache](#cache)
  . Requires 
  `engine: buildx`
  .

### Driver

Set `with.driver` to control which Buildx builder instance runs the build. Atmos creates the builder if it doesn't already exist (`docker buildx create`), then selects it explicitly (`--builder <name>`) — it never mutates or depends on the ambient default builder, so concurrent Atmos runs on the same host don't interfere with each other.

The shorthand form sets `provider` with no `opts`:

```yaml
with:
  driver: docker-container
```

The full form also accepts a builder `name` and driver-specific `opts`:

```yaml
with:
  driver:
    name: atmos                  # builder instance name, defaults to "atmos"
    provider: docker-container   # docker | docker-container | kubernetes | remote
    opts:
      image: mirror.gcr.io/moby/buildkit:buildx-stable-1
```

- **`name`**
  Builder instance name. Defaults to 
  `atmos`
   when omitted. Reusing the same name across runs on the same host makes builder creation idempotent and lets the builder's BuildKit cache persist between builds — an unnamed/randomized builder would leak a fresh, cold container on every run.
- **`provider`**
  Buildx driver: 
  `docker`
   (the daemon's built-in BuildKit — does not support 
  `opts`
  ), 
  `docker-container`
   (a dedicated BuildKit container, the usual choice when 
  `opts`
   is needed), 
  `kubernetes`
   (a BuildKit Pod/Deployment in a cluster), or 
  `remote`
   (connect to an already-running 
  `buildkitd`
  ). These are Buildx's own driver names, passed through as-is — see the 
  [Buildx drivers documentation](https://docs.docker.com/build/builders/drivers/)
  .
- **`opts`**
  Map of driver-specific options, passed as repeated 
  `--driver-opt key=value`
  . Only supported by the 
  `docker-container`
   and 
  `kubernetes`
   drivers.

### Cache

Set `with.cache` to import/export Buildx build cache on the plain (non-bake) build path:

```yaml
with:
  cache:
    from:
      - type: registry
        ref: registry.example.com/app:buildcache
    to:
      - type: registry
        ref: registry.example.com/app:buildcache
        mode: max
        image-manifest: "true"
        oci-mediatypes: "true"
```

- **`from`**
  List of cache sources to import from. Each entry is a raw Buildx cache attribute map (
  `type`
  , 
  `ref`
  , and any type-specific keys), passed through as 
  `--cache-from key=value,key=value`
  .
- **`to`**
  List of cache destinations to export to. Same attribute-map shape as 
  `from`
  , passed through as 
  `--cache-to key=value,key=value`
  .

Each entry's attribute set depends on the Buildx cache backend (`registry`, `local`, `gha`, `s3`, `azblob`, `inline`) — see the [Buildx cache documentation](https://docs.docker.com/build/cache/backends/) for the attributes each backend accepts.

If you build with [`bake`](#bake) instead, define `cache-from`/`cache-to` directly on the bake target — Bake files already support them natively, so `with.cache` isn't needed (or read) in that mode. `with.driver` still applies to bake, since builder selection is independent of build vs. bake.

### Avoiding Docker Hub rate limits

Docker Hub rate-limits anonymous and free-tier pulls, which can throttle both a Dockerfile's `FROM` base images and, when using `engine: buildx` with the `docker-container` driver, the BuildKit image itself. [`mirror.gcr.io`](https://cloud.google.com/artifact-registry/docs/pull-cached-dockerhub-images) is a public pull-through cache for Docker Hub that avoids this. There are three independent places you can point at it, depending on what needs to be mirrored:

- **Base images** — change `FROM alpine:latest` to `FROM mirror.gcr.io/library/alpine:latest` in the Dockerfile itself. Simplest option, no Atmos or host configuration needed, but only covers that one Dockerfile.
- **The BuildKit image** — set `with.driver.opts.image: mirror.gcr.io/moby/buildkit:buildx-stable-1` (see [Driver](#driver) above). Only affects the builder Atmos creates; base image pulls still go straight to Docker Hub unless also mirrored.
- **Every pull on the host** — configure `registry-mirrors` in the Docker daemon (`/etc/docker/daemon.json`, or your CI runner's daemon config):
  ```json
  { "registry-mirrors": ["https://mirror.gcr.io"] }
  ```
  This is host/daemon configuration, not something a `container` step can set — configure it once in your runner image or host provisioning, and every `docker pull` on that host benefits, including base images and any other tool that pulls from Docker Hub.

### Bake

Set `with.bake` to build one or more targets from a `docker-bake.hcl` (or `.json`) definition:

```yaml
with:
  bake:
    file: docker-bake.hcl       # primary bake file
    files:                      # additional bake files
      - docker-bake.override.hcl
    targets:                    # targets to build (use `target` for a single one)
      - api
      - worker
    set:                        # overrides in target.key=value form
      - api.platform=linux/amd64
    vars:                       # bake variables
      VERSION: 1.0.0
    load: true                  # load the result into the local image store
    push: false                 # push the result to the registry
    print: false                # print the resolved definition and exit
```

- **`file` / `files`**
  Primary bake file, and any additional bake files.
- **`target` / `targets`**
  A single target, or a list of targets to build.
- **`set`**
  List of overrides in 
  `target.key=value`
   form.
- **`vars`**
  Map of bake variables.
- **`load`**
  Load the built image into the local image store.
- **`push`**
  Push the built image to the registry.
- **`print`**
  Print the resolved bake definition and exit without building.

## Push

`action: push` pushes an image and its tags to a registry.

```yaml
steps:
  - name: publish
    type: container
    action: push
    provider: docker
    with:
      image: app:local          # source image to push
      tags:                     # target tags (omit to push all existing tags)
        - registry.example.com/app:1.0.0
        - registry.example.com/app:latest
```

- **`image`**
  Source image to push.
- **`tags`**
  Target tags to push. When omitted, Atmos pushes all existing tags for the image.

## Run

`action: run` runs a one-shot container, and is the default when `action` is omitted.

```yaml
steps:
  - name: smoke
    type: container
    action: run
    provider: docker
    env:                        # environment variables come from the step-level env
      LOG_LEVEL: debug
    with:
      image: app:local          # required
      command: ./run-tests.sh   # required
      shell: /bin/sh            # shell used to run the command (default /bin/sh)
      pull: missing             # missing (default) | always | never
      workspace: /workspace     # where the working directory is mounted (default /workspace)
      workspace_read_only: false
      cleanup: always           # always (default) | on_success | never
      user: "1000:1000"         # username or UID:GID
      run_args:
        - --network=host
      mounts:
        - type: bind
          source: ~/.aws
          target: /root/.aws
          read_only: true
      ports:
        - host: 8080
          container: 80
          protocol: tcp
```

- **`image`**
  Required. Container image to run.
- **`command`**
  Required. Command to run inside the container.
- **`shell`**
  Shell used to execute the command. Defaults to 
  `/bin/sh`
  .
- **`pull`**
  Image pull policy: 
  `missing`
   (default), 
  `always`
  , or 
  `never`
  .
- **`workspace`**
  Container path where the working directory is mounted. Defaults to 
  `/workspace`
  .
- **`workspace_read_only`**
  Mounts the workspace read-only when set to 
  `true`
  .
- **`cleanup`**
  Cleanup policy: 
  `always`
   (default), 
  `on_success`
  , or 
  `never`
  .
- **`user`**
  User context for execution. Accepts a username or 
  `UID:GID`
  .
- **`run_args`**
  List of additional arguments passed to the runtime's 
  `run`
   command.
- **`mounts`**
  List of additional volume mounts. See 
  [Mounts](#mounts)
  .
- **`ports`**
  List of published port mappings. See 
  [Ports](#ports)
  .
- **`runtime`**
  Nested runtime block. Set 
  `runtime.host: true`
   to grant the container access to the host container runtime (Docker-out-of-Docker) so it can launch sibling containers — Atmos mounts the runtime socket, runs as root, relabels for SELinux, and sets 
  `DOCKER_HOST`
  . Opt-in and effectively host-root; works on Docker and rootful podman (on rootless podman the socket is unreachable in-container). May also be set inline on the step as 
  `runtime.host`
  .

Environment variables for the container come from the step-level [`env`](/workflows/steps/env) field, not from a field under `with`.

`action: run` also accepts `restart` and `healthcheck` under `with`, mapping onto the Docker Compose shapes. A `healthcheck` is what gates readiness for [background services](#background-services).

### Mounts

`with.mounts` is a list of mount objects:

```yaml
with:
  image: app:local
  command: ls /cache
  mounts:
    - type: bind              # bind (default) | volume | tmpfs
      source: ~/.aws          # host path (bind) or volume name (volume); `~` expands to home
      target: /root/.aws
      read_only: true
    - type: volume
      source: build-cache
      target: /cache
    - type: tmpfs
      target: /tmp/scratch
```

- **`type`**
  Mount type: 
  `bind`
   (default), 
  `volume`
  , or 
  `tmpfs`
  .
- **`source`**
  Host path for 
  `bind`
   mounts or the volume name for 
  `volume`
   mounts. Not used for 
  `tmpfs`
  . 
  `~`
   expands to the host home directory.
- **`target`**
  Required. Path inside the container.
- **`read_only`**
  Mounts the source read-only when set to 
  `true`
  . Defaults to 
  `false`
  .

### Ports

`with.ports` is a list of port mappings:

```yaml
with:
  image: nginx:latest
  command: nginx -g 'daemon off;'
  ports:
    - host: 8080              # host port
      container: 80           # container port
      protocol: tcp           # tcp (default) | udp
```

- **`host`**
  Host port number to publish.
- **`container`**
  Container port number to map to the host port.
- **`protocol`**
  Port protocol: 
  `tcp`
   (default) or 
  `udp`
  .

## Inspect

`action: inspect` displays curated metadata for an image.

```yaml
steps:
  - name: inspect
    type: container
    action: inspect
    provider: docker
    with:
      image: app:local          # image to inspect
```

- **`image`**
  Required. Image to inspect.

## Background services

Set `background: true` on an `action: run` step to start a long-running service detached. Atmos starts the container, then continues to the next step instead of blocking on the container's exit. This is how you bring up an emulator, database, or other dependency that later steps need, without dropping into shell scripts and background jobs.

```yaml
workflows:
  e2e:
    steps:
      - name: emulator
        type: container
        action: run
        background: true
        with:
          image: localstack/localstack
          ports:
            - host: 4566
              container: 4566
          healthcheck:
            test: ["CMD", "curl", "-f", "http://localhost:4566/_localstack/health"]
            interval: 5s
            retries: 10
            start_period: 30s

      - name: apply
        type: atmos
        command: terraform apply vpc -s dev

      - type: cancel
        for: emulator
```

### Readiness via healthcheck

When a background service declares a `healthcheck` under `with`, Atmos blocks until the container reports healthy before running the next step. This reuses the same container health check as container components — no `sleep` guesses, no readiness-polling scripts. A background service "waits until healthy", never "until exit": a long-running service never exits on its own, so readiness is defined by its health check, not its exit code.

You can also gate readiness explicitly with a [`wait`](/workflows/steps/type/wait) step (`{ type: wait, for: [emulator] }`), or wait for every background service at once with [`wait-all`](/workflows/steps/type/wait). The ordinary [`needs`](/workflows/steps) field is unchanged and still expresses step ordering.

### Teardown

Stop and remove a background service with a [`cancel`](/workflows/steps/type/cancel) step (`{ type: cancel, for: emulator }`). If you never cancel it explicitly, Atmos automatically tears down all background services when the workflow ends — including on failure — so a crashed workflow does not leave orphaned containers behind.

- **`background`**
  When 
  `true`
   on an 
  `action: run`
   step, start the container detached and continue the workflow. The step does not block on the container's exit; readiness is gated by its 
  `healthcheck`
   (or an explicit 
  `wait`
   step), and teardown happens via 
  `cancel`
   or automatically at workflow end.
