# atmos container

Run your application and its supporting services locally with Atmos container
components. Like Docker Compose, they let you define services such as an API,
database, and cache, then bring them up together for development and testing.
Keeping those definitions in Atmos stacks lets you share configuration through
imports and inheritance and use the same tooling as the rest of your infrastructure.

Each container component defines one service. Group related services in a
[composition](#compositions) to manage them together. Atmos can build their images,
start and stop containers, and show logs while you work. For a temporary container
that runs a single workflow task, use a [container step](/workflows) instead.

## Usage

```shell
# Image artifact
atmos container build <component> -s <stack>
atmos container push <component> -s <stack>          # pushes every build tag (→ multiple registries)
atmos container pull <component> -s <stack>

# Lifecycle
atmos container run <component> -s <stack>           # one-shot foreground process (run)
atmos container up <component> -s <stack>            # create/start the long-running container
atmos container down <component> -s <stack>          # stop + rm
atmos container start <component> -s <stack>         # start an existing stopped container (inverse of stop)
atmos container restart|stop|rm <component> -s <stack>

# Bulk lifecycle (no component) — see "Bulk operation" below
atmos container up --all                             # all container components in all stacks
atmos container up --all --stack=<stack>             # all container components in one stack
atmos container up                                   # interactive picker (stack + components)

# Inspection
atmos container list                                 # all container components + running state
atmos container ps                                   # running state of all components (optionally -s <stack>)
atmos container ps <component> -s <stack>            # running state of one component
atmos container logs <component> -s <stack>          # one component
atmos container logs --all --stack=<stack>           # all components in a stack
atmos container logs --all --follow                  # tail all components, interleaved + prefixed
atmos container exec <component> -s <stack> -- sh    # run a command / open a shell (new process)
atmos container attach <component> -s <stack>        # attach to the container's main process (PID 1)
```

:::tip Build before start
`up` and `run` build the image automatically when the component declares `build:` and the image
is not present locally. Components that reference an existing `image:` are pulled on demand.
:::

## Configuration

Define container components under `components.container` in your stack manifests. `image`, `build`, and
`run` are **first-class component sections** (siblings of `composition`/`env`/`metadata`) — NOT nested
under `vars`, consistent with the container workflow step:

```yaml
components:
  container:
    api:
      composition: storefront            # first-class composition membership
      image: "localhost:5001/api:{{ .git.sha }}"
      build:
        context: app
        dockerfile: Dockerfile
        tags:
          - "localhost:5001/api:{{ .git.sha }}"
      run:
        command: ./api
        ports:
          - host: 8080
            container: 8080
        mounts:
          - source: .
            target: /workspace
      secrets:
        vars:
          NPM_TOKEN:
            store: app-secrets
            required: true
      env:                               # component env (resolved with secrets)
        PORT: "8080"
        NPM_TOKEN: !secret NPM_TOKEN
```

Inheritance (`metadata.inherits`), catalogs, and deep-merge apply exactly like other component kinds —
abstract base components can carry shared `build`/`run` defaults.

### Path resolution

Relative `build.context` and `run.mounts[].source` values resolve against the container component's
own directory — the same `components.container.base_path` + `component:`/`metadata.component`
mechanism used by Terraform, Helmfile, Kubernetes, and Helm components, **not** the directory `atmos`
happens to be invoked from. A relative `build.dockerfile` then resolves against the resulting
`build.context` (the Docker/Compose convention), not against the component directory directly:

```yaml
# atmos.yaml
components:
  container:
    base_path: components/container   # default
```

```yaml
components:
  container:
    api:
      component: api   # resolves to <base_path>/api, independent of the `atmos` invocation's CWD
      build:
        context: app        # <base_path>/api/app
        dockerfile: docker/Dockerfile.prod  # <base_path>/api/app/docker/Dockerfile.prod (relative to build.context, not <base_path>/api)
```

Container components also support the same JIT `source:` provisioning as other component types — a
component that declares `source:` is auto-vendored into a workdir on demand, and that workdir becomes
the anchor for `build.context`/`build.dockerfile`/`run.mounts[].source` instead of the static
`base_path` directory.

`build.tags` is a list — the build applies all of them to the image, and `push` sends **every** tag, so
listing registry-qualified tags there pushes to multiple registries in one operation (see
[Push to multiple registries](#push-to-multiple-registries)).

### Push to multiple registries

`atmos container push <component>` (and `atmos container push --all`) pushes **every entry in
`build.tags`** — so a single push ships the image to as many registries as you list. The build already
applied each tag to the image locally, so push just sends them in order:

```yaml
components:
  container:
    app:
      image: app:v1                                   # local ref used by run/up
      build:
        context: .
        tags:
          - 1234.dkr.ecr.us-east-1.amazonaws.com/app:v1   # AWS ECR
          - ghcr.io/cloudposse/app:v1                      # GitHub Container Registry
```

```shell
atmos container build app --stack=prod    # builds once, applies both tags
atmos container push app --stack=prod     # pushes to ECR and GHCR
```

Notes:

- Pushes run **in order and fail fast** — the first registry that errors stops the push (fix it and
  re-run; already-pushed registries are simply re-pushed, which is a no-op when the digest is unchanged).
- When a component has **no `build.tags`**, `push` falls back to the single top-level `image` (the
  original behavior).
- Use `--dry-run` to preview exactly which references will be pushed:
  ```shell
  atmos container push app --stack=prod --dry-run
  # ▶ [dry-run] push 1234.dkr.ecr.us-east-1.amazonaws.com/app:v1
  # ▶ [dry-run] push ghcr.io/cloudposse/app:v1
  ```

Authenticate to each registry first (e.g. via `--identity` for cloud registries, or the runtime's own
`docker/podman login`).

### Health checks and restart policies

`run.restart` and `run.healthcheck` are **first-class settings** that mirror Docker Compose, so you no
longer need to hand-write `run.run_args`:

```yaml
components:
  container:
    api:
      image: nginx:alpine
      run:
        # Restart policy → docker/podman --restart
        restart:
          policy: unless-stopped        # no | always | on-failure | unless-stopped
          max_retries: 5                # only used with the `on-failure` policy
        # Health check → docker/podman --health-* (mirrors Compose `healthcheck`)
        healthcheck:
          test: ["CMD-SHELL", "wget -q -O /dev/null http://localhost/ || exit 1"]
          interval: 30s
          timeout: 5s
          retries: 3
          start_period: 10s
          start_interval: 5s
```

**`test`** follows Compose semantics — a string, or a list whose **first element** selects the form:

- **`["CMD", "executable", "arg", …]`**
  Run the command directly. (The CLI runs 
  `--health-cmd`
   through the container's shell, so the args
  are joined into a shell command; for true exec-form, bake a 
  `HEALTHCHECK`
   into the image.)
- **`["CMD-SHELL", "full shell command"]` or a bare string**
  Run the string with the container's default shell (
  `/bin/sh -c`
  ). 
  `test: "curl -f http://localhost || exit 1"`

  is shorthand for 
  `test: ["CMD-SHELL", "curl -f http://localhost || exit 1"]`
  .
- **`["NONE"]` (or `disable: true`)**
  Disable any health check inherited from the image (
  `--no-healthcheck`
  ).

Field-to-flag mapping:

| Field | Flag |
|-------|------|
| `restart.policy` (+ `max_retries`) | `--restart=<policy>[:<max_retries>]` |
| `healthcheck.test` | `--health-cmd` (or `--no-healthcheck`) |
| `healthcheck.interval` | `--health-interval` |
| `healthcheck.timeout` | `--health-timeout` |
| `healthcheck.retries` | `--health-retries` |
| `healthcheck.start_period` | `--health-start-period` |
| `healthcheck.start_interval` | `--health-start-interval` |

Once a health check is configured, `atmos container ps` and `atmos container list` show the resulting
state in a **`HEALTH`** column (`healthy` / `unhealthy` / `starting`, or `-` when no check is defined).
Atmos validates the restart policy and health-check durations up front, so a typo surfaces as a clear
error instead of an opaque runtime failure. For anything not modeled here, `run.run_args` remains the
raw passthrough to `docker/podman create`.

### Runtime selection

The container runtime is auto-detected (Docker first, then Podman). Override it globally in `atmos.yaml`:

```yaml
container:
  runtime:
    provider: auto        # or docker/podman; auto is also the default
    # Auto-start is on by default. Set auto_start: false to opt out.
```

It can also be set with the `ATMOS_CONTAINER_RUNTIME` environment variable.

## Component Instance Identity

A container component instance is identified by `<stack>/<component_type>/<component>`. Atmos projects
that onto a deterministic runtime name and labels:

| Field | Value (example) |
|-------|-----------------|
| Instance | `dev/container/api` |
| Runtime name | `atmos-dev-container-api` |
| Labels | `tools.atmos.stack=dev`, `tools.atmos.component_type=container`, `tools.atmos.component=api`, `tools.atmos.instance=dev/container/api` |

`start`, `ps`, `logs`, `exec`, `attach`, `restart`, `stop`, `rm`, and `down` discover the container by these
labels — there are no local state files to lose or corrupt.

## Lifecycle verbs: `up`/`down` vs `start`/`stop`

The lifecycle has two complementary pairs, mirroring `docker compose`:

- **`up` ↔ `down`** — the full lifecycle. `up` **creates or starts** the named container (building the
  image first if needed); `down` **stops and removes** it (`stop` + `rm`).
- **`start` ↔ `stop`** — toggle the running state of an **existing** container in place. `start` brings a
  stopped container back without recreating it; `stop` halts it without removing it. `restart` is `stop`
  then `start`.

Use `start` to resume a container you previously `stop`ped; use `up` when the container may not exist yet
(it will be created). `rm` removes a stopped container; `down` is the stop-and-remove shortcut.

## `exec` vs `attach`

Both connect you to a running container, but they are not the same:

- **`exec`** starts a **new process** inside the container. With a command after `--` it runs that
  command; with no command it opens a shell (`/bin/sh`). This is how you "shell in."
- **`attach`** connects your terminal to the container's **existing main process (PID 1)** — the
  process the container runs — mirroring `docker attach` / `docker compose attach`. Use it when PID 1
  is itself interactive (a REPL, a foreground server streaming to stdout). Detach with the runtime's
  detach keys (`Ctrl-P` `Ctrl-Q`), which leaves the container running.

## Running state

`atmos container list` shows the running state of every container component — a green ● dot on a TTY,
and `running`/`stopped`/`unknown` text otherwise. A **`HEALTH`** column reports each container's health
(`healthy` / `unhealthy` / `starting`, or `-` when the component defines no
[health check](#health-checks-and-restart-policies)). When no container runtime is available, rows are
reported as `unknown` rather than failing the listing. (Container running state lives here, not in the
generic `atmos list components`, which treats all component kinds uniformly.)

## Bulk operation

The lifecycle verbs that are safe to batch — `build`, `push`, `pull`, `up`, `start`, `restart`, `stop`,
`rm`, and `down` — can operate on **many components at once**. For these verbs the `<component>` argument
is optional, and there are three ways to select what to operate on:

- **`--all`**
  Operate on 
  **every**
   (non-abstract) container component, in dependency-free sorted order. Scope it
  to a single stack with 
  `--stack=<stack>`
  ; without a stack it spans 
  **all**
   stacks.
- **`--all --stack=<stack>`**
  Operate on every container component in just that stack.
- **`--tags <tags>`**
  Operate on container components matching any of the given tags (comma-separated): 
  `--tags=production,tier-1`
  . Components are tagged via an optional 
  `metadata.tags: [...]`
   list in stack manifests. Composes with 
  `--all`
  ; cannot be combined with a single component argument.
- **`--labels <labels>`**
  Operate on container components matching all of the given labels (comma-separated 
  `key=value`
   pairs): 
  `--labels=cost-center=platform,compliance=sox`
  . Components are labeled via an optional 
  `metadata.labels: {...}`
   map in stack manifests. Composes with 
  `--all`
  /
  `--tags`
  ; cannot be combined with a single component argument.
- **no component (interactive)**
  In an interactive terminal, Atmos prompts for a 
  **stack**
   (skipped if only one exists or 
  `--stack`

  is given) and then a 
  **multi-select of components**
   (all pre-selected). Outside a TTY (CI, pipes) this
  is an error — pass 
  `--all`
   or a 
  `<component>`
   instead.

Bulk runs are **continue-on-error**: every selected component is attempted, per-component failures are
reported as they happen, and the command exits non-zero with an aggregated summary if any failed.
Teardown verbs (`down`, `stop`, `rm`) run in **reverse** order of the start verbs so dependents are
removed before what they depend on.

:::note Mutually exclusive
A `<component>` argument and `--all` cannot be combined. `--all` is available on the bulk-capable
lifecycle verbs and on `logs` (see below); `run`, `exec`, and `attach` remain single-component. `ps` and
`list` need no `--all` — omitting the component already shows all (optionally filtered by `--stack`).
:::

### Following logs across components

`logs` supports the same selection (`<component>`, `--all`, or interactive) plus `--follow`/`-f` and
`--tail`:

```shell
atmos container logs api --stack=dev --follow          # tail one component
atmos container logs --all --stack=dev                 # all components, printed in turn
atmos container logs --all --follow                    # tail every component, interleaved
```

When following more than one component, the streams run **concurrently** and each line is prefixed with a
colored, width-aligned **component label** — the same badge style as Atmos log levels, with a distinct
color per component — like `docker compose logs -f`. Where color is unavailable (non-TTY, `NO_COLOR`,
CI), the label degrades to a plain `[api]` / `[worker]` prefix. Press `Ctrl-C` to stop following. Without
`--follow`, multiple components are printed sequentially under an `==> stack/component <==` header.

## Compositions

A composition groups components into a system. Components declare membership via the `composition`
field; the top-level `compositions` section declares the closed set of services:

```yaml
compositions:
  storefront:
    description: Storefront system
    services: [api, worker, database]
```

- Declaring `composition: X` for a service not listed in `compositions.X.services` is a **hard error**.
- A declared service with no component in a stack is **allowed** (membership is closed, fulfillment is open).
- `atmos composition validate <name> -s <stack>` reports fulfilled vs. not-provided-here services.

## Arguments

- **`<component>`**
  The container component to operate on. Required for 
  `run`
  , 
  `exec`
  , and 
  `attach`
  ;

  **optional**
   for the bulk-capable verbs (
  `build`
  , 
  `push`
  , 
  `pull`
  , 
  `up`
  , 
  `start`
  , 
  `restart`
  , 
  `stop`
  ,

  `rm`
  , 
  `down`
  ) and 
  `logs`
  , where omitting it selects components via 
  `--all`
   or an interactive picker (see

  [Bulk operation](#bulk-operation)
  ). For 
  `ps`
  , omitting it lists all components' running state (like

  `list`
  , optionally filtered by 
  `--stack`
  ). Not used by 
  `list`
  .

## Flags

- **`--stack` / `-s` (required for single-component subcommands)**
  The stack the component is defined in. For bulk verbs it scopes the operation to one stack.
- **`--all` (bulk verbs only)**
  Operate on all container components instead of a single one — across all stacks, or one stack when
  combined with 
  `--stack=<stack>`
  . Cannot be combined with a 
  `<component>`
   argument. See

  [Bulk operation](#bulk-operation)
  .
- **`--tags` (bulk verbs only)**
  Filter by tags (comma-separated, matches any): 
  `--tags=production,tier-1`
  . Composes with 
  `--all`
   to narrow the selected set further; cannot be combined with a 
  `<component>`
   argument.
- **`--labels` (bulk verbs only)**
  Filter by labels (comma-separated 
  `key=value`
   or 
  `key:value`
   pairs, matches all): 
  `--labels=cost-center=platform,compliance=sox`
  . Composes with 
  `--all`
  /
  `--tags`
  ; cannot be combined with a 
  `<component>`
   argument.
- **`--follow` / `-f` (logs)**
  Stream logs continuously. With multiple components the streams are interleaved and each line is
  prefixed with the component name. Press 
  `Ctrl-C`
   to stop.
- **`--tail` (logs)**
  Number of lines to show from the end of the logs, or 
  `all`
   (default).
- **`--identity`**
  Authenticate with the given identity before running (e.g. for registry access on build/push/pull).
- **`--dry-run`**
  Print what would happen without touching the runtime.
- **`--` (exec)**
  Everything after 
  `--`
   is the command run inside the container, e.g. 
  `atmos container exec api -s dev -- sh -c 'env'`
  . 
  `attach`
   takes no command — it connects to the existing main process.

## Examples

```shell
# Build the image, start the long-running container, and confirm it is running.
atmos container build api -s dev
atmos container up api -s dev
atmos container list             # api shows a ● running indicator

# Build once and push the image to every registry in build.tags (e.g. ECR + GHCR).
atmos container build api -s dev
atmos container push api -s dev

# Operate the running container (discovered by label).
atmos container ps                       # running state of every component
atmos container logs api -s dev
atmos container exec api -s dev -- sh    # new shell process inside the container
atmos container attach api -s dev        # connect to the container's main process (Ctrl-P Ctrl-Q to detach)

# Stop and later resume the same container in place (no recreate).
atmos container stop api -s dev
atmos container start api -s dev

# Tear down.
atmos container down api -s dev

# Bulk: bring up every container component in a stack, then everywhere.
atmos container up --all --stack=dev
atmos container up --all

# Bulk: tear down a whole stack (reverse order), continue-on-error.
atmos container down --all --stack=dev

# Bulk: bring up only components tagged "production" and labeled cost-center=platform.
atmos container up --all --tags production --labels cost-center=platform

# Bulk: interactive picker (TTY) — choose a stack, then components.
atmos container up

# Report a composition's fulfilled vs. not-provided services.
atmos composition validate storefront -s dev
```
