atmos container
Use the atmos container subcommands to build, run, and operate container components — stack-scoped,
Atmos-native, persistent containers. One component is one service. Atmos owns the image artifact
(build/push/pull) and an optional long-running named container lifecycle (up/start/ps/logs/exec/
attach/restart/stop/rm/down), discovered by labels derived from the canonical component instance
address — not from local state files.
A container component is the per-service building block. A set of container components grouped by a
composition is effectively "your own Compose" — Atmos orchestrates a multi-container
local system with no compose.yaml. This is distinct from the ephemeral type: container
step, which is docker run --rm and workflow-scoped; the component is
declarative, addressable infrastructure.
Usage
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:
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.
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
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:
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
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,pushfalls back to the single top-levelimage(the original behavior). - Use
--dry-runto preview exactly which references will be pushed: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:
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-cmdthrough the container's shell, so the args are joined into a shell command; for true exec-form, bake aHEALTHCHECKinto 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 fortest: ["CMD-SHELL", "curl -f http://localhost || exit 1"]. ["NONE"](ordisable: 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:
container:
runtime:
provider: podman # or docker, or empty for auto-detect
auto_start: true # auto-init/start the Podman machine when needed
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.upcreates or starts the named container (building the image first if needed);downstops and removes it (stop+rm).start↔stop— toggle the running state of an existing container in place.startbrings a stopped container back without recreating it;stophalts it without removing it.restartisstopthenstart.
Use start to resume a container you previously stopped; 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:
execstarts 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."attachconnects your terminal to the container's existing main process (PID 1) — the process the container runs — mirroringdocker 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-PCtrl-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). 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.
- no component (interactive)
- In an interactive terminal, Atmos prompts for a stack (skipped if only one exists or
--stackis given) and then a multi-select of components (all pre-selected). Outside a TTY (CI, pipes) this is an error — pass--allor 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.
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:
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:
compositions:
storefront:
description: Storefront system
services: [api, worker, database]
- Declaring
composition: Xfor a service not listed incompositions.X.servicesis 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, andattach; optional for the bulk-capable verbs (build,push,pull,up,start,restart,stop,rm,down) andlogs, where omitting it selects components via--allor an interactive picker (see Bulk operation). Forps, omitting it lists all components' running state (likelist, optionally filtered by--stack). Not used bylist.
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. --follow/-f(logs)- Stream logs continuously. With multiple components the streams are interleaved and each line is
prefixed with the component name. Press
Ctrl-Cto 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'.attachtakes no command — it connects to the existing main process.