Skip to main content

Container Components

Container components are 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, discovered by labels derived from the canonical component instance address — not from local state files. You declare them with the same stack-based configuration used for Terraform, Helmfile, Packer, and Ansible.

For the conceptual overview and command walkthrough, see Using Containers. This page is the stack-manifest configuration reference.

Available Configuration Sections​

Container components are declared under components.container and use first-class sections (siblings of metadata — not nested under vars):

image
The image reference to run (and the default push/pull target). May be set directly or produced by build.
build
How to build the image — context, dockerfile, tags, build_args, target, no_cache, pull, and more. Reuses the same shape as the type: container workflow step.
run
How to run the long-lived container — command, ports, mounts, user, run_args, pull, restart, and healthcheck.
env
Container application environment, resolved with secrets. Injected into the running container — not read from run.
composition
Optional composition membership — groups this service into a larger system.
metadata
Component behavior and inheritance (e.g. metadata.type: abstract for catalog base components).
hooks
Lifecycle event handlers.

Component Structure​

A typical container component configuration:

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
restart:
policy: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80/health"]
interval: 30s
retries: 3
env: # component env (resolved with secrets)
PORT: "8080"

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

Port Bindings​

Use run.ports to publish container ports. host accepts a port number or a quoted IP:port string. container is the container port, and protocol defaults to tcp.

components:
container:
portal:
image: my-portal:local
run:
ports:
- host: "127.0.0.1:3000"
container: 3000

A numeric host: 3000 preserves the container runtime's default interface binding. Use host: "127.0.0.1:3000" for IPv4 loopback or host: "0.0.0.0:3000" for all IPv4 interfaces. IPv6 requires brackets, for example host: "[::1]:3000" for loopback or host: "[::]:3000" for all IPv6 interfaces. The address must be an IP literal, not a hostname or interface name.

Loopback binding narrows where the runtime listens, but it is not a firewall. Docker Engine versions before 28.0.0 may expose localhost-published ports to hosts on the same network segment. See Docker's port publishing documentation for details.

Omitting host or setting it to 0 requests an automatically assigned host port. To request one on a specific address, use host: "127.0.0.1:0" or host: "[::1]:0". Host ports must be between 0 and 65535; quoted port numbers such as host: "3000" are also accepted. The same syntax works in container workflow steps, workflow sandboxes, background container services, and emulators.

Changes to port bindings require removing and recreating the container with atmos container down followed by atmos container up.

Component-Type Defaults​

Define defaults for all container components:

# Apply to all container components
container:
run:
restart:
policy: unless-stopped

# Individual components
components:
container:
api:
image: localhost:5001/api:latest

Complete Example​

stacks/deploy/dev.yaml
import:
- catalog/container/_defaults

vars:
stage: dev

components:
container:
api:
composition: storefront
build:
context: app
dockerfile: Dockerfile
tags:
- localhost:5001/api:latest
run:
ports:
- host: 8080
container: 80
env:
PORT: "8080"

worker:
image: localhost:5001/worker:latest
run:
command: ./worker
env:
QUEUE_URL: "redis://localhost:6379"

Running Container Commands​

Atmos provides commands that wrap the container 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

atmos container exec allocates a TTY only when stdin is a terminal. It also works in scripts and pipelines, preserving piped stdin without requesting a TTY.

Additional Runtime Arguments​

run.run_args forwards additional arguments to Docker or Podman for both atmos container up and atmos container run, for example run_args: [--init]. An explicit --network or --net argument takes precedence over Atmos's automatic shared stack network.

Changes to runtime arguments require removing and recreating the container with atmos container down followed by atmos container up.

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.

Containers in the same stack automatically join a shared network and can resolve each other by a <stack>-<component> DNS alias, with no configuration required — see Networking.

CI Reporting​

When CI is enabled, atmos container build and atmos container push write a summary for each image to the job summary: the tag, digest, image ID, runtime settings, environment variables, labels, and layers. The job summary always keeps the full report, and every pushed reference gets its own section.

Pull request comments​

The same summary can also be posted as a pull request comment. Atmos posts a comment only when all of these are true:

  • ci.enabled and ci.summary.enabled are on, and Atmos runs under a detected CI provider, so the summary reached the provider.
  • ci.comments.enabled is true. Comments are off by default because they write to the pull request.
  • A pull request number is known. When none is known, for example on a push to a branch, the comment is posted as a commit comment on the run's commit instead.
  • The job token can write comments. On GitHub Actions, grant pull-requests: write, and contents: write for commit comments.

Atmos keeps one comment per image, not one per tag. The comment key is container:image:<registry/repository> with the tag and digest removed, so ghcr.io/acme/api:sha-1a2b3c and ghcr.io/acme/api:latest update the same comment. A build or push replaces the body in place, and a push of several tags lists every reference pushed in that run. Images with different repositories, for example one pushed to two registries, get separate comments.

GitHub rejects comment bodies over 65,536 characters. Atmos truncates the comment body at 65,000 characters and ends it with a note that points to the job summary, which keeps the full report. A comment that cannot be posted logs a warning that names the image and never fails the build or push.

When comments are off, Atmos does not post a comment and does not print a preview of one to the log. Outside CI, the summary is rendered locally as before.

To change the Markdown, point ci.templates.container.image at a template file under ci.templates.base_path:

atmos.yaml
ci:
templates:
base_path: ".atmos/ci/templates"
container:
image: "image.md"

See CI templates for the template syntax and the data available to the image template.