Cais
Run your self-hosted stack from the terminal, without leaving your compose file behind.

Demo

Cais is a keyboard-driven terminal UI for a self-hosted stack running on
Docker Compose. It reads your compose.yml and groups services the way you think about
them. Nothing extra to host and nothing listening on a port.
Your compose file stays the source of truth. Every change Cais makes is
written back into that file, comments and key order intact, so docker compose
on the command line and Cais never disagree about what your stack is.
What it does
Groups, not a flat list of containers. A group is a Compose profiles:
tag. Start all services in a group with one key, tail all their logs with another,
and see at a glance which are running, which are healthy, and on what ports.

Everything you need to check your services. Ports, restart policy,
networks, volumes, depends_on, healthcheck, image reference and resource
limits, beside live memory, CPU, network and disk I/O for the running container.

A working link to the thing you just started. The config details view shows
a link to the live service. Ctrl-click it, or copy it with y.
The app never opens a browser itself.
A healthcheck in one keypress. h on a selected service opens a picker of
templates: Postgres, MariaDB, Redis and nginx, each using a probe tool that
ships in the image it targets, plus a generic HTTP fallback. Enter writes a
validated healthcheck: block straight into the compose file.
New services without leaving the terminal. n on the Services page asks
for a name and an image reference, then writes a minimal image: fragment
straight into the compose file and opens the inline editor on it. Ports,
volumes and everything else get added in the same YAML you would have
hand-written, with live validation the whole way.
The .env beside your compose file gets a page of its own. The Env tab
lists every variable with values masked by default. v reveals a value, c
copies it, add/edit/delete go through small modals with a confirm on delete,
and o opens the whole file in an inline editor. Writes are line-preserving,
so comments and the variables you did not touch survive.
Edit the compose file in place, as YAML. e opens the service's own
fragment in an inline editor: real YAML, not a form, so every Compose field is
reachable. It validates as you type, auto-indents on Enter, indents with
tab/shift+tab, and refuses to write a fragment that would not parse as
Compose.

Logs without leaving. l streams docker compose logs -f for a service or
a whole group in an overlay, with follow mode and scrollback.

More features
The compose file itself, syntax-highlighted and scrollable. E opens it in
your $EDITOR; b browses the other compose files in the same directory and
switches which one the app is driving.

Three themes, previewed live as you move the cursor: cais-dark,
cais-dusk and cais-day. Enter applies and persists your choice;
Esc restores the one you started with. Plus Catppuccin Mocha,
Gruvbox Dark, Tokyo Night, Nord, Dracula, Solarized Dark, One Dark,
Everforest Dark, Rosé Pine and Kanagawa Wave for a total of
thirteen.

Also: create, rename, and delete groups; change which services belong to them;
confirm-guarded removes; a status re-poll every five seconds so panels reflect
changes made outside the app; and a ? overlay listing every key, with the ones
that do nothing on the current screen dimmed.
Install
go install github.com/filipemolina/cais@latest
Or build from source:
git clone https://github.com/filipemolina/cais.git
cd cais
make build # installs to $(go env GOPATH)/bin, usually ~/go/bin
There are no downloadable binaries yet. A v* tag builds them (Linux and
macOS, amd64 and arm64), but the releases stay in draft until the launch
work in the roadmap, so go install or a clone is the way in for now.
Requirements: Docker with the Compose plugin on your PATH, a terminal, and
Go 1.26+ to build. If something is missing or the daemon is not running, the
app says which one failed (missing Docker, missing Compose plugin, unreachable
daemon, or permissions) and gives you the exact command to fix it.
Use
cais # the compose file in this directory
cais --dir ~/homelab/media # resolve one in that directory
cais --file ~/homelab/compose.prod.yml # open exactly this file
With no flags it auto-detects compose.yaml, compose.yml,
docker-compose.yaml, docker-compose.yml, the same order Docker uses. The
file that wins is named in the footer, and it is passed as --file to every
docker compose call, so the commands always act on the file the panels
describe. In a directory with no compose file at all, the app offers to write
one.
Keys
? lists every key in context. The ones worth knowing:
| Key |
Action |
1 2 3 4 |
Groups / Services / Files / Env ([ and ] step through them) |
↑ ↓ k j |
Move the cursor; the details panel follows it |
Tab |
Move focus between the list and the details panel |
s t r p x |
Start · Stop · Restart · Pull · Remove (x confirms first) |
l |
Stream logs for the service, or for every service in the group |
y |
Copy a service's URL (when it publishes one) |
h |
Add a healthcheck from the template picker |
e |
Edit: a service's YAML inline, or a group's membership |
E |
Open the whole compose file in $EDITOR (the .env file on the Env page) |
n |
New: a group on the Groups page, a service on the Services page (name and image, then the inline editor opens on it), a variable on the Env page |
R d |
Rename group · Delete group (d confirms first) |
/ |
Filter the list by name |
u |
Docker disk and memory usage overlay |
T ? a q |
Themes · Help · About · Quit |
Start/Stop/Restart/Pull/Remove run docker compose underneath, scoped to every
service in the group on the Groups page, to one service on the Services page.
Status
Early, and honest about it. Everything shown above works today; what follows is
what does not, in the order it is being closed. The sequence and the reasoning
live in docs/ROADMAP.md:
- Write safety: a backup before every edit. Every compose and
.env
write is snapshotted into .cais/backups/ next to the compose file before
it lands, so a bad edit can be undone. Each source gets its own folder
(compose_yaml/, _env/), and a copy is kept per write, deduped when the
content has not changed. Up to 500 past copies are kept per file; older ones
are pruned. The .env file is backed up too, so its secrets rest in the
store alongside the compose file, and a .cais/.gitignore keeps the store
out of git status. This is the store only: a browse and restore UI to
travel back through those copies is the next step (v0.4.0).
- Blank lines between services are not preserved across a write. Comments,
quoting and key order are. This is accepted rather than fixed: a blank line
inside a block scalar (
command: |) is part of the string, and silently
rewriting your data is a worse failure than losing your spacing.
- A narrow terminal shows less, not worse. Under roughly 130 columns the
keybinding bar starts dropping hints, and a narrow details panel drops
columns from the member table, widest and least important first, and never
q quit, ? help or the service's own name.
Issues and ideas are welcome, and at this stage they still change the
direction.
Built with
Go, Bubble Tea /
Lip Gloss for the UI, and
compose-go, the same parser
Docker itself uses, for the file. Docker actions shell out to the
docker compose CLI rather than binding the SDK, so what the app does is what
you would have typed.
License
MIT. © 2026 Filipe Molina.