Repository navigation
CLI Containers
These are the manifest-free container verbs. They act on one container (or a
list of containers) by its real name, with no project manifest involved, and
they are a thin pass to Apple's container CLI with ac's daemon ownership
contract, command echo and --json handling layered on top.
For a whole stack declared in a manifest, use the project form instead: see
Project commands. ac stop shop-redis and
ac shop stop redis reach the same container, but only the project form
resolves short service names and knows about readiness.
Image and registry verbs live in Images and registries, the noun groups in Daemon and system.
- Naming a container
- Daemon gating
- TTY detection
runcreate- Shared run and create flags
startstoprestartrmexecshlogsinspectkillcpexportstatstopport
Every command that takes a container accepts two spellings of the same name:
- the real container name,
shop-redis -
project/service,shop/redis, which is rewritten toshop-redis
Resolution happens against a live container ls -a snapshot. Three failure
modes, all loud:
- an unknown name lists the containers that do exist
(
no such container: web; none exist right nowwhen there are none) - a name that is a project rather than a container is rejected with a
pointer to
ac <project> status - naming nothing at all on
stop,rmorkillis an error, not a silent no-op (see the--allwarning below)
$ ac logs shop/redis
$ ac logs shop-redis # identical
$ ac logs shop
error: 'shop' is a project, not a container
try: ac shop status, or name a service directly (ac logs shop-<service>)The verbs split three ways, and the split decides whether ac will start the daemon for you.
| Group | Commands | Behaviour |
|---|---|---|
| Reads |
exec, sh, logs, inspect, cp, export, stats, top, port
|
Require a running daemon. If it is down they fail with container daemon is not running; start it with `ac system start` or any `ac <project> start` rather than starting one. |
| Mutations that leave nothing running |
stop, rm, kill
|
Also require a running daemon (there is nothing to act on otherwise), then run the cross-project refcount check afterwards so an ac-owned daemon can be released. |
| Mutations that leave a container running |
run, create, start, restart
|
Ensure the daemon, record ownership if ac started it, and spawn the supervisor, so the daemon stays up and is still reaped once the container goes. |
Containers created by ac run and ac create are labelled ac.managed=1.
That label is what makes the supervisor count them, so a daemon started for a
one-off ac run is not stopped out from under the container it just started.
Apple container fails with ENODEV when -t is passed and there is no TTY,
which is what breaks execs in scripts and CI. So ac passes -t only when
stdin and stdout are both terminals, whatever you asked for. Asking for -t
without a terminal prints:
not allocating a TTY: stdin and stdout are not both terminals
and continues without it. ac run in the foreground and ac exec add -t
automatically when both are terminals, even without -t on the command line.
ac exec always passes -i; ac run passes -i when -i was given or when
the run is non-detached on a terminal.
Run a container from an image, docker run style.
Underneath: container run [options] <image> [command...], always with
--label ac.managed=1 appended.
ac run [OPTIONS] <IMAGE> [COMMAND...]
| Flag | Default | What it does |
|---|---|---|
--rm |
off | Remove the container when it exits. |
Everything else comes from the shared option set, see Shared run and create flags.
COMMAND... is a trailing var-arg with hyphens allowed, so everything after
the image is handed to the container verbatim. Global flags such as --json
and --quiet must therefore come before the subcommand.
After a detached run that succeeded, ac prints one
http://localhost:<port> line per published TCP port (UDP ports are skipped).
Sharp edge: the URLs are read back by inspecting the container by name, so they
are printed only when you passed --name. A detached run without --name
starts fine and prints nothing.
The exit code is the exit code of container run.
$ ac run -d --name web -p 3000:3000 my-app:dev
http://localhost:3000
$ ac run --rm -it docker.io/library/alpine:3.20 sh
/ #
$ ac --json run -d --name web -p 3000:3000 my-app:devCreate a container without starting it, docker create style. Start it later
with ac start.
Underneath: container create [options] <image> [command...], same flags as
run minus --progress, which container create does not accept. Passing
--progress warns (container create has no --progress; ignoring it) and the
flag is dropped.
ac create [OPTIONS] <IMAGE> [COMMAND...]
| Flag | Default | What it does |
|---|---|---|
--rm |
off | Remove the container when it exits. |
create never allocates a TTY on its own: -i is passed through when given,
-t is recorded on the command line but not added, and no automatic TTY
detection runs (there is no process to attach to yet).
$ ac create --name web -p 3000:3000 -e NODE_ENV=production my-app:dev
$ ac start webOne option set, flattened into both run and create (src/cli/run_opts.rs).
The only difference is --progress, which create ignores.
| Flag | Default | run | create | What it does |
|---|---|---|---|---|
-d, --detach
|
off | yes | ignored | Run in the background and print the container name. create never starts anything, so this has no effect there. |
--name <NAME> |
generated | yes | yes | Name for the container. Also what makes run -d able to print published URLs. |
-i, --interactive
|
off | yes | yes | Keep stdin open. |
-t, --tty
|
off | yes | recorded, not passed | Request a TTY. Honoured only when stdin and stdout are both terminals; see TTY detection. |
-u, --user <NAME|UID[:GID]>
|
image default | yes | yes | User for the process. |
--uid <UID> |
image default | yes | yes | User ID for the process. |
--gid <GID> |
image default | yes | yes | Group ID for the process. |
-w, --workdir <PATH> (alias --cwd) |
image default | yes | yes | Initial working directory inside the container. |
--entrypoint <CMD> |
image default | yes | yes | Override the image entrypoint. |
--init |
off | yes | yes | Run an init process that forwards signals and reaps children. |
--init-image <IMAGE> |
built in | yes | yes | Custom init image. |
--cidfile <PATH> |
none | yes | yes | Write the container ID to this path. |
| Flag | Default | run | create | What it does |
|---|---|---|---|---|
-e, --env <KEY=VALUE>
|
none | yes | yes | Environment entry. Repeatable. A bare KEY inherits the host value. |
--env-file <PATH> |
none | yes | yes | File of KEY=VALUE entries. Repeatable. |
| Flag | Default | run | create | What it does |
|---|---|---|---|---|
-p, --publish <SPEC>
|
none | yes | yes | Publish a port, [host-ip:]host-port:container-port[/protocol]. Repeatable. |
--publish-socket <SPEC> |
none | yes | yes | Publish a socket, host_path:container_path. Repeatable. |
-v, --volume <SPEC>
|
none | yes | yes | Bind mount, source:target. Repeatable. |
--mount <SPEC> |
none | yes | yes | Mount in long form, type=<>,source=<>,target=<>,readonly. Repeatable. |
--tmpfs <PATH> |
none | yes | yes | Add a tmpfs mount at the given path. Repeatable. |
--shm-size <SIZE> |
runtime default | yes | yes | Size of /dev/shm, for example 64M. |
--read-only |
off | yes | yes | Mount the root filesystem read-only. |
| Flag | Default | run | create | What it does |
|---|---|---|---|---|
-c, --cpus <N>
|
runtime default | yes | yes | CPUs to allocate. This sizes the container's VM, not a cgroup. |
-m, --memory <SIZE>
|
runtime default | yes | yes | Memory to allocate, with optional K, M, G, T or P suffix. |
--ulimit <LIMIT> |
none | yes | yes | Resource limit, <type>=<soft>[:<hard>]. Repeatable. |
| Flag | Default | run | create | What it does |
|---|---|---|---|---|
--platform <os/arch[/variant]> |
host | yes | yes | Platform for a multi-platform image. |
-a, --arch <ARCH>
|
host | yes | yes | Architecture for a multi-arch image. --platform wins. Note -a here is not --all; on stop, rm and kill the same letter means --all. |
--os <OS> |
linux |
yes | yes | OS for a multi-OS image. --platform wins. |
--scheme <http|https|auto> |
auto |
yes | yes | Registry scheme used when pulling. |
--progress <STYLE> |
auto | yes | ignored with a warning | Progress output style for the implicit pull. |
--max-concurrent-downloads <N> |
runtime default | yes | yes | Maximum concurrent image layer downloads. |
| Flag | Default | run | create | What it does |
|---|---|---|---|---|
--network <NAME[,mac=..][,mtu=..]> |
default |
yes | yes | Attach to a network. Non-default networks need macOS 26 or newer. |
--dns <IP> |
inherited | yes | yes | DNS nameserver address. Repeatable. |
--dns-domain <DOMAIN> |
inherited | yes | yes | Default DNS domain. |
--dns-option <OPTION> |
none | yes | yes | DNS option. Repeatable. |
--dns-search <DOMAIN> |
none | yes | yes | DNS search domain. Repeatable. |
--no-dns |
off | yes | yes | Do not configure DNS in the container at all. |
| Flag | Default | run | create | What it does |
|---|---|---|---|---|
-l, --label <KEY=VALUE>
|
none | yes | yes | Container label. Repeatable. ac always appends ac.managed=1 on top of whatever you pass. |
--cap-add <CAP> |
none | yes | yes | Add a Linux capability, for example CAP_NET_RAW or ALL. Repeatable. |
--cap-drop <CAP> |
none | yes | yes | Drop a Linux capability. Repeatable. |
-k, --kernel <PATH>
|
bundled | yes | yes | Custom kernel path. |
--runtime <HANDLER> |
default | yes | yes | Runtime handler. |
--ssh |
off | yes | yes | Forward the SSH agent socket into the container. |
--rosetta |
off | yes | yes | Enable Rosetta in the container. |
--virtualization |
off | yes | yes | Expose virtualization capabilities to the container. |
$ ac run -d --name api \
-p 8080:8080 -e PORT=8080 --env-file .env \
-v "$PWD/data:/data" --cpus 4 --memory 4g \
--label team=platform my-api:dev
http://localhost:8080Start one or more stopped or created containers.
Underneath: container start once per container, because container start
takes a single id. Failures are collected and reported at the end, and the
command exits non-zero if any container failed.
ac start [-a] [-i] <CONTAINER...>
| Flag | Default | What it does |
|---|---|---|
-a, --attach
|
off | Attach stdout and stderr. |
-i, --interactive
|
off | Attach stdin. |
At least one container is required. Every container that starts prints
<name> started followed by its published http://localhost:<port> URLs.
$ ac start web api
web started
http://localhost:3000
api started
http://localhost:8080Stop one or more running containers.
ac stop [-t SECS] [-s SIG] [-a] [CONTAINER...]
| Flag | Default | What it does |
|---|---|---|
-t, --time <SECS>
|
unset | Seconds to wait before killing the container. Passed as --time N only when you give it; otherwise Apple container uses its own default and ac's kill deadline assumes 5. |
-s, --signal <SIG>
|
none | Send this signal instead. Bypasses ac's escalation ladder and runs a plain container stop --signal <SIG> [--time N]. |
-a, --all
|
off | Stop every running container on the daemon. |
Without -s, ac uses the same ladder as a project stop, because a wedged
container ignores container stop and container kill alike:
-
container stop [--time N] <container>under a kill deadline ofmax(2 * time, 20)seconds, wheretimedefaults to 5. - If the container is still running,
container kill --signal KILLunder a 10 second deadline, with a warning. - If it is still running, ac finds that container's own
container-runtime-linuxshim (matched on--uuid <name>viapgrep -f) andkill -9s it, waits 2 seconds, and re-checks.
Success is decided by observed state, not by an exit code: after each rung
ac re-reads the container list. Anything still running at the end is reported
as still running: <names> and the command exits non-zero.
-a/--all stops every container the daemon has, including other
projects' and other people's. Bare ac stop with no target is an error rather
than a no-op, precisely so nobody reaches for -a to make it do something:
stop needs a container name, or --all
for a whole project stack: ac <project> stop
$ ac stop -t 30 web
web stopped
$ ac stop -s TERM web # no escalation, plain container stop --signal TERMStop then start, docker restart style. The stop half uses the full escalation ladder above, and the daemon is not released in between, so restarting the only running container never bounces an ac-owned daemon.
ac restart [-t SECS] <CONTAINER...>
| Flag | Default | What it does |
|---|---|---|
-t, --time <SECS>
|
unset | Seconds to wait before killing the container during the stop half. When omitted, ac's kill deadline assumes 5. |
Containers that are not currently running are simply started. Published URLs
are printed for each container that comes back. At least one container is
required. Anything that fails to come back up is reported as
failed to restart: <names> and the command exits non-zero.
$ ac restart web
web restarted
http://localhost:3000Remove containers. Volumes survive; images are ac image rm.
ac rm [-f] [-a] [CONTAINER...]
| Flag | Default | What it does |
|---|---|---|
-f, --force
|
off | Remove even if the container is running. |
-a, --all
|
off | Remove every container on the daemon, passed through as container rm --all. |
Alias: ac delete.
Same rule as stop: with neither a name nor --all it errors instead of doing
nothing. --all is a whole-daemon blast radius.
$ ac rm -f web apiRun a command in a running container.
Underneath: container exec -i [-t] [flags] <container> <command...>. The -i
is always present.
ac exec [-i] [-t] [-d] [-e KEY=VALUE] [-w PATH] [-u USER] <CONTAINER> <COMMAND...>
| Flag | Default | What it does |
|---|---|---|
-i, --interactive
|
off | Accepted for docker muscle memory. -i is always passed underneath, so this changes nothing. |
-t, --tty
|
off | Request a TTY. Added only when stdin and stdout are both terminals; a TTY is also added automatically in that case even without -t. |
-d, --detach
|
off | Run detached (--detach). |
-e, --env <KEY=VALUE>
|
none | Environment entry for the exec'd process. Repeatable. |
-w, --workdir <PATH>
|
container default | Working directory inside the container. |
-u, --user <NAME|UID[:GID]>
|
container default | User to run as. |
COMMAND... is required, is a trailing var-arg, and allows hyphens, so global
flags must come first. The exit code is the exit code of the command inside the
container.
$ ac exec -it web sh
$ ac exec -e PGPASSWORD=pass -u postgres shop-postgres psql -c 'select 1'Note: container exec into a container whose exec channel has wedged will
block until you Ctrl-C. ac does not bound this one, only its own readiness
probes.
Open a shell in a running container. ac probes for bash with
container exec <c> sh -c 'command -v bash' and runs bash when it is there,
otherwise sh. It then goes through the same exec path, asking for a TTY (so
you get one when your terminal has one).
ac sh <CONTAINER>
No flags. Alias: ac shell.
$ ac sh web
root@web:/#Fetch container logs.
Underneath: container logs [--follow] [--boot] [-n N] <container>.
ac logs [-f] [-n N] [--boot] <CONTAINER>
| Flag | Default | What it does |
|---|---|---|
-f, --follow
|
off | Follow log output. |
-n, --tail <N>
|
all | Number of lines to show from the end. |
--boot |
off | Show the VM boot log instead of the container's stdio. |
Exactly one container. To fan out across a whole stack with per-service
prefixes and colours, use ac <project> logs.
$ ac logs -f -n 100 shop-redisDetailed information about containers, pretty-printed.
Underneath: container inspect <container...>, one call for all of them. The
output is parsed as JSON and re-printed indented; under --json it is emitted
as the single stdout document. If it does not parse as JSON it is printed
through unchanged.
ac inspect <CONTAINER...>
No flags. At least one container is required. For images use
ac image inspect.
$ ac inspect web
$ ac --json inspect web api | jq '.[].configuration.publishedPorts'Send a signal to containers.
Underneath: container kill --signal <SIG> <container...>.
ac kill [-s SIG] [-a] [CONTAINER...]
| Flag | Default | What it does |
|---|---|---|
-s, --signal <SIG>
|
KILL |
Signal to send. |
-a, --all
|
off | Signal every running container on the daemon (container kill --all). |
As with stop and rm, naming nothing without --all is an error, and
--all is a whole-daemon blast radius. The refcount check runs afterwards.
$ ac kill -s HUP webCopy files between a container and the host.
Underneath: container cp <src> <dst>.
ac cp <SRC> <DST>
No flags. Alias: ac copy.
Each side is examined independently. A side is treated as a container path when
it contains a :, the part before the : is non-empty, and the whole string
is not an existing path on the host. In that case the head is resolved as a
container name (including the project/service spelling) and rewritten to the
real container name. Anything else is passed through untouched.
$ ac cp ./seed.sql shop/postgres:/tmp/seed.sql # becomes shop-postgres:/tmp/seed.sql
$ ac cp web:/app/build ./buildEvery ac cp prints a warning first:
container cp is unreliable in Apple container 1.1.0; prefer `ac exec` with shell redirection
That is not paranoia. In 1.1.0 copies into a container can silently no-op
while exiting 0, and copies out can hang forever; killing the hung cp can
wedge the container so stop and kill stall too. ac passes cp through and
cannot mask this.
Export a container's filesystem as a tar archive.
Underneath: container export -o <output> <container>.
ac export [-o PATH] <CONTAINER>
| Flag | Default | What it does |
|---|---|---|
-o, --output <PATH>
|
<container>.tar |
Output path. |
Apple container refuses to export a running container, so ac checks the
observed state first and fails with an actionable message rather than letting
the runtime produce a confusing one:
web is running; Apple container can only export a stopped container
stop it first: ac stop web
$ ac stop web
$ ac export web -o /tmp/web.tar
exported web to /tmp/web.tarLive resource usage.
Underneath: container stats [--no-stream] [container...].
ac stats [--no-stream] [CONTAINER...]
| Flag | Default | What it does |
|---|---|---|
--no-stream |
off | Take one sample and exit instead of streaming. |
With no containers named, every running container is included (ac passes no names through, letting the runtime decide).
--json implies --no-stream: ac runs
container stats --no-stream --format json and kills it after 20 seconds, so
a wedged runtime cannot hang the command. The parsed document is emitted on
stdout as-is.
$ ac stats --no-stream web api
$ ac --json stats web | jq '.[].cpu'Processes running inside containers.
There is no container top, so ac runs ps aux through container exec per
container, falling back to plain ps when ps aux fails (busybox images).
Each container's name is printed before its table.
ac top [CONTAINER...]
No flags. With no containers named, every running container is used; if there
are none, ac warns no running containers and exits 0.
$ ac top web
web
PID USER TIME COMMAND
1 root 0:00 node server.jsNote this is exec-based, so it inherits the exec channel's failure mode: a container under heavy load whose exec channel has wedged will not report.
Published port mappings for a container.
There is no container port, so ac reads
configuration.publishedPorts out of container inspect and formats it.
ac port <CONTAINER>
No flags. Exactly one container.
Human output is one line per mapping, containerPort/proto -> hostAddress:hostPort,
with 0.0.0.0 assumed when the runtime reports no host address and tcp
assumed when it reports no protocol. Under --json the raw publishedPorts
array is emitted.
When the container publishes nothing, ac says so and, because Apple containers
get a routable 192.168.64.x address, prints the container's IP (with the
prefix length stripped) so you can reach it directly anyway.
$ ac port web
3000/tcp -> 0.0.0.0:3000
$ ac port shop-redis
shop-redis publishes no ports
reachable directly at 192.168.64.4-
-qmeans three different things across the CLI, and none of them is the global quiet flag. Global quiet is--quietorAC_QUIET=1, with no short form.-qis "names only" onac psandac image ls, and--build-quietonac build. -
Trailing arguments swallow global flags.
run,create,exec,cpandmachineforward everything after their target, so--json,--quietand--no-colormust come first:ac --json run ..., neverac run --json .... The same applies to the--format jsonrewrite, which is deliberately skipped once one of those verbs has been seen. -
-ais overloaded.--archonrun,createandbuild,--attachonstart,--allonstop,rm,kill,ps,image pruneandsystem prune. -
container runsometimes exits non-zero having actually started the container. The project start path sleeps and re-checks; the bareac runpasses the exit code straight through, so trustac psover a surprising exit code. -
Every underlying
containercommand is echoed to stderr, dimmed and prefixed with$, so any step can be copied and re-run by hand. Suppress with--quietorAC_QUIET=1.
- CLI reference: the index, the two invocation forms and the docker-to-ac translation table.
- Project commands: the same verbs against a manifest, with ordered startup and readiness.
-
Images and registries:
ac build,ac pull,ac pushand the image store. -
Daemon, system and host-level commands:
ac ps,ac statusand the ownership contract these verbs obey. -
Global flags and invocation-wide behaviour:
--json,--quiet, TTY detection and exit codes.
Auto-generated from docs/; edit the docs in the repo, not the wiki.