A terminal UI for putting together a compose.yaml fast — either
service by service, or as a ready-made multi-service stack (a database
plus its admin UI, a blog platform plus its database, and so on) in a
single keystroke. Built with Bubble Tea
and Lipgloss.
It also validates what you build as you go — not just YAML syntax, but common Docker footguns: ports quietly published on every network interface, an empty or hardcoded secret, a database with no volume (so its data disappears the moment the container is recreated).
It's also safe to use as a viewer/editor on a compose.yaml (or docker-compose.yml) you
already have. Import keeps every field verbatim, including ones this
tool's form can't edit (command, networks, container_name,
labels, env_file, secrets, x-* extensions, and so on) — editing
one service through the form and saving never drops the rest of the
file.
-
3-pane TUI: services list, service config, live YAML preview — the preview updates as you type, so you always see the file you're about to save.
-
First-launch screen: an empty config shows a small button menu (add a service / browse presets & stacks / import a file) instead of three empty panes — navigable by arrow keys, Enter, or a mouse click.
-
Presets — drop in a single well-configured service (Postgres, Redis, Nginx, MySQL, MongoDB) instead of typing out
image:,ports:, etc. by hand. -
Stacks — drop in several services at once, already wired together (
depends_on, matching hostnames, shared env vars). Covers common combinations like a database with its admin UI (Postgres + PgAdmin, MySQL + phpMyAdmin, Mongo + Mongo Express, Adminer + Postgres), a CMS or app platform with its database (WordPress + MySQL, Ghost + MySQL, Nextcloud + Postgres, Nextcloud + Redis + MariaDB, Directus + Postgres), and common infra pairings (Prometheus + Grafana, Elasticsearch + Logstash + Kibana, Redis + RedisInsight, Gitea + Postgres, Metabase + Postgres, n8n + Postgres, Portainer + Watchtower). If a stack's default service name is already taken, it's renamed automatically (db→db-2) rather than refusing or silently overwriting anything. -
Validation (
Ctrl+V, scrollable) — three separate reports, so "komposer thinks this is risky" and "Docker Compose would reject this outright" never get confused with one another:- Errors — komposer's own required-field, port-format,
restart-policy, and
depends_on-reference checks (including adepends_on: condition: service_healthythat points at a service with no healthcheck configured — Compose refuses to start that stack, so this is an Error rather than a Warning), - Warnings — non-blocking advisories: a port published on every
network interface (Docker's default) with a suggested
127.0.0.1:...fix, an environment variable that looks like a secret (by name) with an empty or hardcoded value, a recognized database/stateful image with no volume configured or no healthcheck configured, Postgres published on the network with noPOSTGRES_PASSWORDset, an image with no tag (or an explicit:latest) — either resolves to whateverlatesthappens to point to right now, which can pull a different image on everydocker compose pullwith no change to the file itself; a digest reference (image@sha256:...) doesn't trigger this, since it's already fully pinned, a bind-mounted Docker socket (/var/run/docker.sock),cap_adding a capability that meaningfully widens host access (ALL,SYS_ADMIN,NET_ADMIN,SYS_PTRACE,SYS_MODULE),network_mode: host,pid: host, and asecurity_optthat disables seccomp/AppArmor confinement, - Compose specification — checked against the official
Compose Specification JSON Schema
(vendored under
pkg/composer/schema/), independent of the two checks above.
Warnings never block saving — they're advisory, shown separately from hard errors.
- Errors — komposer's own required-field, port-format,
restart-policy, and
-
Import (
Ctrl+O) an existingcompose.yamlordocker-compose.yml— parses the raw YAML tree rather than a fixed struct, so any field this tool doesn't have explicit support for is captured and re-emitted untouched on export instead of being silently dropped. Bothdepends_onforms (short list and long map-with-condition) are understood. -
Explicit save (
Ctrl+S) — prompts for a path (defaulting tocompose.yaml) and asks before overwriting an existing file. If you quit (qorEsc) with unsaved changes, you get the same prompt instead of losing them silently. -
Editing a service: Esc discards your edits (asking for confirmation only if you actually changed something) rather than saving automatically;
Ctrl+Ssaves explicitly. -
Mouse wheel and keyboard both scroll the services list, the YAML preview, and the validation report.
Grab the archive for your platform from the Releases page, then:
Linux / macOS
tar -xzf komposer_*_<os>_<arch>.tar.gz
chmod +x komposer
sudo mv komposer /usr/local/bin/ # or anywhere on your $PATHmacOS will refuse to run the binary the first time because it isn't notarized (Gatekeeper). Either right-click it in Finder and choose "Open" once, or clear the quarantine flag from a terminal:
xattr -d com.apple.quarantine komposerWindows
Unzip komposer_*_windows_<arch>.zip and run komposer.exe from a
terminal (PowerShell or Windows Terminal — it's a TUI, it needs a real
terminal, not double-clicking from Explorer). Put it somewhere on your
PATH (e.g. %LOCALAPPDATA%\Microsoft\WindowsApps) if you want to run
it as just komposer from anywhere.
Windows SmartScreen may warn about an unsigned binary the first time — click "More info" → "Run anyway". This is expected for an unsigned open-source binary; there's no code-signing certificate behind these builds.
<arch> is amd64 for regular 64-bit Intel/AMD machines and arm64
for Apple Silicon Macs or ARM64 Windows/Linux.
go install github.com/casablanque-code/komposer@latestThis puts a komposer binary in $(go env GOPATH)/bin — make sure
that's on your PATH. Installs a specific version instead of the
latest with @vX.Y.Z (e.g. @v0.1.0).
git clone https://github.com/casablanque-code/komposer.git
cd komposer
go build -o komposer .Requires Go 1.24.2+. Run it directly without a separate build step
with go run . instead, if you'd rather not produce a binary.
./komposer| Key | Action |
|---|---|
↑ / ↓ |
move between the 3 buttons |
Enter, or a mouse click |
activate the highlighted/clicked button |
a / Ctrl+P / Ctrl+O |
jump straight to add / presets & stacks / import, same as their buttons |
| Key | Action |
|---|---|
← / → |
switch focus between the three panes |
↑ / ↓, mouse wheel |
services list: move selection · YAML preview: scroll |
a |
add a new service (prompts for a name) |
d |
delete the selected service (prompts for confirmation) |
Enter / e |
edit the selected service (center pane must be focused) |
| Key | Action |
|---|---|
↑ / ↓ |
move within a multi-line field (ports/environment/volumes); at the top/bottom edge, switches to the previous/next field |
Tab / Shift+Tab |
switch field directly |
Enter |
insert a new line, in ports/environment/volumes |
Ctrl+S |
save the service and return to the normal view |
Esc |
discard changes and return — asks for confirmation only if anything actually changed |
| Key | Action |
|---|---|
Ctrl+P |
open the preset/stack picker — ←/→ switches between the Presets and Stacks tabs, ↑/↓ navigates, Enter adds the selection |
Ctrl+V |
run validation — shows errors and warnings for the current config, scrollable with ↑/↓ or the mouse wheel |
Ctrl+O |
import an existing compose.yaml or docker-compose.yml |
| Key | Action |
|---|---|
Ctrl+S |
save — prompts for a path (default compose.yaml), asks before overwriting an existing file |
q / Esc |
quit — if there are unsaved changes, opens the same save prompt with an explicit "quit without saving" option; quits immediately otherwise |
Ctrl+C |
quit immediately, no prompt |
pkg/composer— the domain model: services, presets, stacks, YAML import/export, and validation. No TUI dependencies — usable as a library on its own.internal/tui— the Bubble Tea/Lipgloss terminal UI.
go build ./...
go vet ./...
go test ./... -raceCI (.github/workflows/ci.yml) runs go vet, a gofmt check, and
the test suite (with -race) on Linux, macOS, and Windows for every
push and pull request against main.
Tagged releases (vX.Y.Z) are built and published automatically by
.github/workflows/release.yml via GoReleaser
— see .goreleaser.yaml for the build/archive configuration.