Skip to main content

Using Containers

Atmos natively supports container components — stack-scoped, persistent containers. One component is one service. Atmos owns the image artifact (build/push/pull) and an optional long-running named container lifecycle (up/ps/logs/exec/restart/stop/rm/down), discovered by labels derived from the canonical component instance address — not from local state files.

For the complete command reference, see the atmos container documentation. To operate a group of fulfilled services together, see atmos composition.

This is different from the ephemeral type: container workflow step, which is docker run --rm and workflow-scoped. The component is declarative, addressable infrastructure; the step is procedural sequencing.

Stack Configuration

Container component config uses first-class sections (image, build, run) — consistent with the container workflow step, NOT nested under vars:

components:
container:
api:
composition: storefront # composition membership (optional)
image: localhost:5001/api:latest
build: # build the image
context: app
dockerfile: Dockerfile
tags:
- localhost:5001/api:latest
run: # run configuration
command: ./api
ports:
- host: 8080
container: 80
mounts:
- source: .
target: /workspace
env: # component env (resolved with secrets)
PORT: "8080"

Inheritance (metadata.inherits), catalogs, mixins, and deep-merge work exactly like other component kinds — define abstract base components with shared run/build defaults and inherit them.

Host runtime access (Docker-out-of-Docker)

A container that must launch and manage sibling containers (Testcontainers-style suites, tools that shell out to docker) can drive the host container runtime with run.runtime.host:

components:
container:
integration-tests:
run:
runtime:
host: true # mount the host runtime socket, run as root, set DOCKER_HOST

This is the same runtime.host flag available on emulator components and type: container workflow steps. It is opt-in and effectively host-root; it works on Docker and rootful podman (on rootless podman the socket is unreachable in-container — use podman machine set --rootful or Docker). It is independent of kernel-capability privileged.

Lifecycle

atmos container build api -s dev # build the image from `build`
atmos container up api -s dev # create/start the long-running container (build-on-missing)
atmos container list # all container components + running state
atmos container ps api -s dev # show running state
atmos container logs api -s dev # stream logs
atmos container exec api -s dev -- sh # run a command inside the container
atmos container down api -s dev # stop + rm

Each instance is named and labeled from its canonical address <stack>/container/<component> (e.g., atmos-dev-container-api), so lifecycle commands discover it by label — there are no local state files.

atmos container list shows a running/stopped/unknown indicator for each container component.

Networking

Every container component in a stack automatically joins that stack's shared network and gets a DNS alias of <stack>-<component>, so containers can reach each other by name without any configuration — similar to the default network Docker Compose creates for a project. Emulator components join the same per-stack network, so a plain container and an emulator in the same stack can resolve each other too (e.g. a container's run.command connecting to http://dev-localstack:4566).

This applies to both atmos container up (long-lived) and atmos container run (one-shot) containers, and to a workflow's type: container step when the step resolves a stack. A workflow step registers under its own <stack>-<step-name> alias (the step's name:, not a component name — a step has no component identity to key off), so a workflow-driven test runner can be reached by its alias too, while it reaches components.container instances and emulators by their <stack>-<component> alias. It's best-effort: if the container runtime can't create or join a network, the container falls back to the default bridge — the container still runs, it's just unreachable by name from its stack peers (host port publishing via run.ports is unaffected either way). The shared network is created once per stack and is never deleted by Atmos. Unlike Docker Compose, the per-stack network remains after teardown.

Compositions

A composition groups components into a system. Declare membership with the composition field; the top-level compositions section declares the closed set of services:

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

Run atmos composition validate storefront -s dev to see which services are fulfilled vs. not provided in a stack. Use the first-class composition lifecycle when you want Atmos to operate the fulfilled members together:

atmos composition list -s dev
atmos composition up storefront -s dev
atmos composition ps storefront -s dev
atmos composition logs storefront -s dev --tail=100
atmos composition down storefront -s dev

If you omit the composition name from a stack-scoped lifecycle or read command, Atmos targets all compositions with fulfilled members in that stack. Startup and read commands process composition names alphabetically and services in declared order; teardown commands use the reverse order.

Try It

Explore a complete, working example of the container component kind's full lifecycle.

atmos container component lifecycle
 
00:00.0 / 00:00.0

Container Components

This example demonstrates the container component kind: stack-scoped, Atmos-native, persistent containers. One component is one service. Atmos owns the image artifact (build/push/pull) and a long-running named container lifecycle (up/ps/logs/exec/restart/stop/rm/down), discovered by labels derived from the canonical component instance address — not from local state files.

This is different from the ephemeral type: container step (docker run --rm, workflow-scoped). The component is declarative, addressable infrastructure; the step is procedural sequencing.

Layout

atmos.yaml # container.runtime + compositions
Dockerfile # image for the `worker` component
stacks/deploy/dev.yaml # container.api + container.worker

Two container components are defined in the dev stack:

  • api — a long-running web service from the public nginx:alpine image, published on localhost:8080.
  • worker — a background worker built from the local Dockerfile.

Both declare composition: storefront.

Lifecycle

# Build the worker image from the local Dockerfile.
atmos container build worker -s dev

# Start the long-running containers.
atmos container up api -s dev
atmos container up worker -s dev

# See which containers are running (look for the ● running indicator).
atmos container list

# Inspect and operate the running containers (discovered by label).
atmos container ps api -s dev
atmos container logs worker -s dev
atmos container exec api -s dev -- sh -c 'nginx -v'

# Tear down (stop + remove).
atmos container down api -s dev
atmos container down worker -s dev

# Now they show as stopped.
atmos container list

Runtime name and labels

The api component in the dev stack maps to:

instance: dev/container/api
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

All lifecycle commands discover the container by these labels, so there is no local state file to lose or corrupt.

Compositions

atmos.yaml declares a storefront composition with services api, worker, and database. The first two are fulfilled by components in this stack; database is declared but not provided here — which is allowed (membership is a closed contract, but fulfillment is open). Declaring composition: for a service not listed under compositions.storefront.services is a hard error.

Requirements

A working Docker or Podman runtime. With Podman, container.runtime.auto_start in atmos.yaml initializes/starts the Podman machine automatically.