Run in a container¶
A container image packages the Asterism runtime so the same local runtime runs
anywhere a container runs — your laptop's Docker, a small VPS, any container host.
It is the same single-agent serve or chat channel process you run
on the command line, just carried somewhere else.
This is packaging and portability, not a security boundary. The runtime's separation guarantees are exactly the same inside the container as on the host — no more, no less. The image lets you run the same runtime elsewhere; it does not add isolation or containment around an agent. Confinement of agent code is a later phase, in the container as on your machine.
Use the published image¶
The released image is published to the GitHub Container Registry and runs natively on
both linux/amd64 and linux/arm64 — Intel/AMD servers and Apple Silicon Macs alike,
with no --platform flag:
$ docker pull ghcr.io/qmilab/asterism # or :0.4.0 to pin a version
$ docker run --rm ghcr.io/qmilab/asterism --help
Tags: latest, the exact version (e.g. 0.4.0), and the minor line (0.4). The examples
below use the short local tag asterism; substitute ghcr.io/qmilab/asterism anywhere to
run the published image instead of building your own.
Build the image¶
From a checkout of the repo:
$ docker build -t asterism .
That produces a minimal image with the asterism CLI as its entrypoint: whatever
you pass after the image name is the command line.
$ docker run --rm asterism --help
Where state lives: the /data volume¶
The container itself is disposable. Everything an install owns — the SQLite
store, each agent's workspace, the access token — lives under /data, which you
mount as a named volume so it outlives any single container:
$ docker volume create asterism-data
Every command below mounts that volume with -v asterism-data:/data. The working
directory inside the container is /data, so the runtime puts its .asterism/ home
there and finds it again on the next run — state survives docker rm and upgrades.
Set it up: init and create an agent¶
Run these as one-off containers that share the volume. They write to /data and
exit:
$ docker run --rm -v asterism-data:/data asterism init
$ docker run --rm -v asterism-data:/data asterism new writer --soul casual-helper --trust notify
Add secrets and skills the same way — each is a short-lived container over the same volume:
$ docker run --rm -i -v asterism-data:/data asterism secrets add writer OPENAI_API_KEY
A model is configured the usual way:
either with docker run … asterism config … (written to the volume) or by passing
the provider key and model coordinates as environment variables on the serve
container below.
Serve one agent over HTTP¶
This is the long-lived container. Three things differ from the one-offs:
$ export ASTERISM_HTTP_TOKEN=$(openssl rand -hex 32)
$ docker run -d --name writer-serve \
-v asterism-data:/data \
-e ASTERISM_HTTP_TOKEN \
-e OPENAI_API_KEY=sk-… \
-p 4831:4831 \
asterism serve writer --host 0.0.0.0
--host 0.0.0.0— the runtime's default binds127.0.0.1, which is only the loopback inside the container and cannot be published. Binding0.0.0.0lets the published port reach it. (Override the port with--port; the default is4831, matched by-p 4831:4831.)-e ASTERISM_HTTP_TOKEN— the access token every request must carry. Generating it into your shell first (theexportabove) and passing the bare-e ASTERISM_HTTP_TOKENforwards that value into the container and keeps it in your shell, so thecurlbelow sends the same token. Always inject it for an exposed container. The endpoint is default-deny and will not serve a request without the bearer token. If you omit the variable, the runtime falls back to generating a per-agent token and printing it once to the container log (docker logs writer-serve) — fine for a quick local try, but an injected secret is the right choice for anything exposed, because it is stable and never depends on what is on the volume.-p 4831:4831— publish the port. Reach it with the token:
$ curl -H "Authorization: Bearer $ASTERISM_HTTP_TOKEN" \
http://localhost:4831/agents/writer/runs
Run a chat channel instead¶
A Telegram or Discord channel is outbound — it dials the chat platform, so it needs no published port, just its bot token, a model key, and the allow-list of channel ids permitted to drive the agent:
$ docker run -d --name writer-discord \
-v asterism-data:/data \
-e ASTERISM_DISCORD_TOKEN=… \
-e ASTERISM_DISCORD_ALLOW=4035…21 \
-e OPENAI_API_KEY=sk-… \
asterism channel discord writer
The allow-list is the channel's only access control, so set it. Don't know the id
yet? Start the container without ASTERISM_DISCORD_ALLOW once — that is
discovery mode: every message is
refused and the bot replies only with the sender's channel id. Read it from docker
logs writer-discord, then re-run with the variable set. (Telegram is identical with
ASTERISM_TELEGRAM_TOKEN / ASTERISM_TELEGRAM_ALLOW.)
Environment and volume reference¶
| Mount / variable | For | Notes |
|---|---|---|
-v asterism-data:/data |
every command | The install's state. Use the same volume for setup and for serving so they share one install. |
-p 4831:4831 |
serve (exposed) |
Publish the HTTP port; pair with --host 0.0.0.0. Not needed for channels. |
ASTERISM_HTTP_TOKEN |
serve (exposed) |
The bearer token requests must carry. Inject it for anything exposed. |
ASTERISM_TELEGRAM_TOKEN |
channel telegram |
The bot token from @BotFather. |
ASTERISM_TELEGRAM_ALLOW |
channel telegram |
Comma-separated chat ids allowed to drive the agent. Empty ⇒ discovery mode. |
ASTERISM_DISCORD_TOKEN |
channel discord |
The bot token from the Discord developer portal. |
ASTERISM_DISCORD_ALLOW |
channel discord |
Comma-separated channel ids allowed to drive the agent. Empty ⇒ discovery mode. |
provider key (e.g. OPENAI_API_KEY) |
starting runs | Needed to start runs; a serve container's read endpoints work without one. |
ASTERISM_MODEL_* / ASTERISM_API_KEY |
model selection | Choose the model through the environment instead of asterism config. |
ASTERISM_RECALL_EMBED_URL / _MODEL / _KEY |
opt-in recall provider | The local embeddings endpoint, for an agent opted into local recall (config recall-provider). Optional; unset ⇒ the built-in keyword ranker. _KEY only if the endpoint needs a token. |
No secret is ever baked into the image — every credential is injected at run time
with -e, exactly as it is sourced from the environment everywhere else in Asterism.
The destructive-action gate still applies¶
A container loosens the trust model not one bit. A
destructive action still pauses for explicit confirmation, even for an autonomous
agent. With no terminal attached, an HTTP run parks at awaiting_confirmation and
waits until you approve it out of band over HTTP; a chat run
asks for a /confirm reply. Running in a container does not mean unattended approval.
Same runtime, same boundaries¶
The image is a way to run this runtime somewhere else, with the same separate lives it has on your machine: one container serves one agent, it is not a path to another agent, and it adds no route around the trust gate. It also does not, on its own, harden the runtime against hostile agent code — that containment is a later phase. Treat the container as portability, and keep the published endpoint behind its token.