# 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` (`docker` or `podman`, auto-detected when 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            # docker | podman (auto-detected when 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
```

- **`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`
  .

### 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.
