# CoolDeck – keyboard-first terminal UI for Coolify

> Source: <https://github.com/Resetnak/cooldeck>
> Published: 2026-09-02 08:36:33+00:00

**Your Coolify fleet, one keystroke away.**

Deployments, logs, restarts and instance switching for [Coolify](https://coolify.io) -
from the terminal you already have open.

*No browser tab. No daemon. No token on screen. On purpose.*

**English** · [Čeština](/Resetnak/cooldeck/blob/main/README.cs.md)

[Quick Start](#-quick-start) · [Features](#-key-features) · [Fleet tail](#-fleet-tail) · [MCP](#-your-fleet-in-your-agent) · [Comparison](#-how-it-compares) · [Keyboard Shortcuts](#-keyboard-shortcuts) · [Installation](#-installation) · [Configuration](#-configuration) · [Security](#-security) · [Contributing](/Resetnak/cooldeck/blob/main/CONTRIBUTING.md)

**Try the whole UI in one line - no Coolify instance, no token, no network:**

```
# macOS (Homebrew)
brew install resetnak/tap/cooldeck && cooldeck --demo

# Linux & macOS (install script)
curl -fsSL https://raw.githubusercontent.com/Resetnak/cooldeck/main/install.sh | sh && cooldeck --demo
```

Prefer Go? `go install github.com/resetnak/cooldeck/cmd/cooldeck@latest`

. Prefer a binary? Every
[release](https://github.com/Resetnak/cooldeck/releases/latest) ships Linux, macOS and Windows
archives plus `.deb`

/`.rpm`

/`.apk`

. `--demo`

runs against deterministic sample data - the same data
the golden tests render - so you can judge the product before you hand it a token.

Checking whether a deploy went through should not cost you a browser tab, a login, and three clicks
through a dashboard. **CoolDeck** puts the same fleet - statuses, deployment history, runtime logs and
the deploy button - into a terminal window you can leave open next to your editor, and drives all of it
from the keyboard.

It talks to the Coolify REST API directly. No proxy, no agent, no daemon: one static binary that reads your config, pulls a token out of your OS keyring, and renders.

```
 COOLDECK  Demo   ● connected   DEMO                              12 apps  |  refreshed just now
────────────────────────────────────────────────────────────────────────────────────────────────
 RESOURCES        │ STATUS       APPLICATION       BRANCH         DEPLOYED  DOMAIN      │ billing-api
                  │ ▲ Degraded   billing-api       main            47m ago  api.billi…  │ ▲ Degraded
 > Applications 12│ ◐ Restarting ingest-dashboard  feat/charts  1m 30s ago  -           │
   Deployments  33│ ● Running    landing-web       main                21s  landing.e…  │ Branch   main
   Instances     1│ ○ Queued     vault-web         main                 4s  vault.exa…  │ Commit   9f31c2e
   Diagnostics    │ ■ Stopped    billing-worker    main             2h ago  -           │ Deploy   47m ago
                  │ ● Running    shipyard-api      main             3m ago  shipyard.…  │
────────────────────────────────────────────────────────────────────────────────────────────────
  ↑↓ navigate   enter details   d deploy   r restart   s start/stop   / filter   ? more     1/12
```

**Look before you connect**:`cooldeck --demo`

- the full UI on deterministic fake data, offline.**Create a Coolify API token**: in Coolify,*profile → API tokens*. Give it the least privilege you can live with (`read`

plus only the write scopes you actually want).**Connect**:`cooldeck setup`

walks you through URL, token and keyring storage.**Use it**:`cooldeck`

. Press`?`

for the key map,`:`

for the command palette,`/`

to filter.

One degraded app, start to finish: / to filter, enter for the detail, l for runtime logs, d to redeploy behind a confirmation, 2 a to watch it land in the active queue. Rendered from

[cassette.tape](/Resetnak/cooldeck/blob/main/cassette.tape).

**🖥️ The whole fleet on one screen**: status, branch, last deploy and domain for every application, with filtering (`/`

) and sorting (`S`

).**🔎 Detail without a context switch**: overview, deployment history, runtime logs and configuration as tabs on the same screen.**🛰️ Fleet tail**: mark applications with`space`

, press`t`

, and read their runtime logs interleaved in one buffer, each line named and coloured by the application it came from - the view Coolify's web UI cannot give you.**📜 Real log ergonomics**: follow, pause, wrap, in-buffer search with`n`

/`N`

, copy the match, clear the buffer,`+`

/`-`

to widen or narrow the line window.**🚀 Operations behind a confirmation**: deploy, force deploy, restart, start/stop - every destructive action asks first, and only one mutation runs at a time.**🛟 Honest about failure**: a refresh that fails keeps the last good data on screen behind a stale banner instead of blanking the list. See[When Coolify blinks](#-when-coolify-blinks).**🔀 Several instances, one session**: switch fleets with`3`

without restarting; add, edit and delete local instance entries from inside the TUI.**⌨️ Keyboard-first, mouse-optional**: vi-flavoured bindings borrowed from`lazygit`

and`k9s`

, a command palette (`:`

/`Ctrl+K`

) for the day you forget one, and a`?`

overlay that always shows the truth - every hint is generated from a single`KeyMap`

.**🎨 Eight themes, responsive layout**: auto, dark, light, Dracula, Catppuccin, Nord, Gruvbox, Tokyo Night; three-pane at 150+ columns, single column when the window is small.**🔐 Tokens you never see**: OS keyring by default, and tokens are kept out of the UI, the logs, the toasts and the diagnostics export by construction.**🤖 An MCP server in the same binary**:`cooldeck mcp`

hands your fleet to an agent - read-only until you say otherwise. See[Your fleet, in your agent](#-your-fleet-in-your-agent).**🧪 Offline demo mode**:`--demo`

is a full implementation of the same service interface, which is also what the golden snapshot tests render.

**The one view Coolify's web UI cannot give you.** Mark the applications you care about with
`space`

, press `t`

, and their runtime logs arrive interleaved in a single buffer - every line named
and coloured by the application it came from. One incident, one screen, instead of a browser tab per
service.

Three services marked, one buffer: space to mark, t to tail, / to search across all of them, w to wrap. Rendered from

[tail.tape](/Resetnak/cooldeck/blob/main/tail.tape).

`f`

follows, `space`

pauses, `c`

copies the merged buffer, `esc`

goes back. Coolify serves runtime
logs as whole snapshots rather than a stream, so CoolDeck polls one request per marked application
every 4 seconds, staggers them, and merges the replies by timestamp - up to five applications at a
time, and it says so when it drops the rest. The concurrency model is
[ADR 0007](/Resetnak/cooldeck/blob/main/docs/decisions/0007-fleet-tail-concurrency.md).

`cooldeck mcp`

speaks the [Model Context Protocol](https://modelcontextprotocol.io) over stdin/stdout,
so an agent can ask what is running, why a build failed, and what the logs say - through the same use
cases the TUI uses. No second HTTP client, no separate token, no daemon.

A real JSON-RPC session against --demo - handshake, tool discovery, two calls, then the opt-in. Rendered from mcp.tape.

**It cannot touch your production by default.** The read-only surface is `list_applications`

,
`get_application`

, `list_deployments`

, `get_runtime_logs`

, `get_deployment_logs`

and
`get_instance_info`

. `--allow-mutations`

adds `deploy_application`

, `restart_application`

,
`start_application`

and `stop_application`

- and nothing behind them asks for confirmation, because an
agent has no one to ask. Grant it deliberately, and prefer a token scoped to the instance you are
willing to let it operate.

```
// Point any MCP client at the binary you already have:
{ "mcpServers": { "cooldeck": { "command": "cooldeck", "args": ["mcp"] } } }
```

Try it before you wire it up: `cooldeck mcp --demo`

serves the same tools against the offline demo
fleet, so you can watch an agent work without a Coolify instance in the loop.

📖 ** Full guide: docs/mcp.md** - client setup, every tool and its arguments, what to
decide before granting mutations, and troubleshooting.

CoolDeck is not a replacement for the Coolify web UI - it is the fast path for the handful of things you do twenty times a day.

| Tool | Great at | Where CoolDeck differs |
|---|---|---|
Coolify web UI |
Everything - creating resources, editing env vars, managing servers | CoolDeck is read-and-operate only, but gets you from "is it up?" to "redeployed" in a few keystrokes, with no tab switch |
`curl` + `jq` |
Scripting, one-off queries | CoolDeck gives you the same API with statuses, history and logs in one live view, and refuses to let a typo trigger a production deploy without confirming |
k9s / lazydocker |
The container layer underneath | CoolDeck speaks Coolify's model - applications, projects, environments, deployments - not raw containers |

Everything it does is an ordinary Coolify API call, so nothing here locks you in or out of the web UI.

Dashboards that clear the screen the moment a request fails are worse than useless during an incident. A failed refresh in CoolDeck keeps the last good snapshot, flags it as stale, and tells you how old it is. When the instance comes back, the next refresh heals it - no restart, and you keep your place in the list.

Command palette, a simulated outage ( F2 in demo mode), and the recovery. Rendered from outage.tape.

Every API call is bounded by a 20-second timeout, every request kind is cancellable, and stale replies from a superseded request are dropped rather than rendered.

| Shortcut | Action |
|---|---|
`j` / `k` or `↑` / `↓` |
Move selection |
`g` / `G` |
First / last item |
`Ctrl+D` / `Ctrl+U` |
Page down / up |
`Tab` / `Shift+Tab` |
Next / previous pane |
`Enter` / `Esc` |
Open / back |
`1` `2` `3` `4` |
Applications · Deployments · Instances · Diagnostics |
`q` / `Ctrl+C` |
Back or quit / force quit |

| Shortcut | Action |
|---|---|
`d` / `D` |
Deploy / force deploy (confirms) |
`r` |
Restart (confirms) |
`s` |
Start or stop (confirms) |
`l` / `L` |
Runtime logs / build log |
`b` / `o` |
Open primary domain / repository in the browser |
`c` |
Copy application UUID |
`Space` / `t` |
Mark for the fleet tail / open the fleet tail |
`S` |
Cycle sort: status → name → last deploy |
`R` |
Manual refresh |

| Shortcut | Action |
|---|---|
`/` |
Filter applications (`status:` , `branch:` , free text) - or search the log buffer |
`:` / `Ctrl+K` |
Command palette; disabled commands show why |
`?` |
Help overlay with the complete key map |
`Ctrl+T` / `Ctrl+W` |
Cycle theme / toggle compact layout |

| Shortcut | Action |
|---|---|
`Space` |
Pause / resume polling (in the applications list, `Space` marks for the fleet tail instead) |
`f` / `w` |
Follow tail / wrap long lines |
`/` · `n` · `N` |
Search · next match · previous match |
`c` |
Copy the buffer, or the current match |
`+` / `-` |
More / fewer lines fetched (this session) |
`Ctrl+L` |
Clear the local buffer |

Full map: press `?`

in the app, or read [docs/keybindings.md](/Resetnak/cooldeck/blob/main/docs/keybindings.md).

**No runtime dependencies.** Every option below leaves you with a single static binary; only building
from source needs a toolchain (Go **1.26.6+**, no CGO).

```
brew install resetnak/tap/cooldeck
cooldeck --demo
```

Upgrades come with `brew upgrade`

like anything else. The tap publishes a cask, which Homebrew on
Linux does not support - on Linux, use the install script below.

```
curl -fsSL https://raw.githubusercontent.com/Resetnak/cooldeck/main/install.sh | sh
```

Detects your platform, **verifies the checksum**, and drops the binary in `~/.local/bin`

. Override
with `COOLDECK_INSTALL_DIR`

, or pin a version with `COOLDECK_VERSION=v0.2.1`

. Read it first if you
would rather not pipe a script into a shell - [it is short](/Resetnak/cooldeck/blob/main/install.sh).

```
# Resolve the newest tag once, then pick your package manager:
VER=$(curl -fsSLI -o /dev/null -w '%{url_effective}' \
  https://github.com/Resetnak/cooldeck/releases/latest | sed 's|.*/v||')
BASE=https://github.com/Resetnak/cooldeck/releases/download/v$VER

# Debian / Ubuntu
curl -fsSLO "$BASE/cooldeck_${VER}_linux_amd64.deb"
sudo dpkg -i "cooldeck_${VER}_linux_amd64.deb"

# Fedora / RHEL
sudo rpm -i "$BASE/cooldeck_${VER}_linux_amd64.rpm"

# Alpine
curl -fsSLO "$BASE/cooldeck_${VER}_linux_amd64.apk"
sudo apk add --allow-untrusted "cooldeck_${VER}_linux_amd64.apk"
```

`.deb`

, `.rpm`

and `.apk`

are built for `amd64`

and `arm64`

on every release - swap `amd64`

for
`arm64`

above if that is your machine.

Download an archive for your platform from [Releases](https://github.com/Resetnak/cooldeck/releases/latest),
unpack it, and put `cooldeck`

on your `PATH`

:

```
tar xzf cooldeck_*_Darwin_arm64.tar.gz     # or Linux_x86_64, Linux_arm64, Darwin_x86_64
sudo mv cooldeck /usr/local/bin/
cooldeck version
```

Windows ships as a `.zip`

. Every release carries a `checksums.txt`

; verify before you trust it:

```
shasum -a 256 -c checksums.txt --ignore-missing
```

Archives for Linux and macOS (`amd64`

& `arm64`

) and Windows (`amd64`

) are built by
[GoReleaser](/Resetnak/cooldeck/blob/main/.goreleaser.yaml) from `v*`

tags.

```
go install github.com/resetnak/cooldeck/cmd/cooldeck@latest
git clone https://github.com/Resetnak/cooldeck.git
cd cooldeck
make build          # -> bin/cooldeck, with version/commit/date baked in
./bin/cooldeck --demo
install -m 0755 bin/cooldeck ~/.local/bin/cooldeck
```

CoolDeck reads one TOML file. Find it - and check it - with:

```
cooldeck config path
cooldeck config validate
```

| OS | Default directory |
|---|---|
Linux |
`$XDG_CONFIG_HOME/cooldeck` or `~/.config/cooldeck` |
macOS |
`~/Library/Application Support/cooldeck` |
Windows |
`%AppData%\cooldeck` |

```
version = 1                          # schema version; a newer one is rejected, never guessed at
default_instance = "production"
theme = "auto"                       # auto|dark|light|dracula|catppuccin|nord|gruvbox|tokyo-night
refresh_interval = "10s"             # dashboard poll (minimum 3s)
log_refresh_interval = "2s"
log_lines = 300                      # default log window (10–10000)
confirm_destructive_actions = true
confirm_deploy = false               # set true to confirm ordinary deploys too

[ui]
nerd_font = "auto"                   # auto|on|off
compact_mode = "auto"
mouse = true

[instances.production]
name = "Production"
url = "https://coolify.example.com"
token_source = "keyring"             # keyring|command|env|plaintext
token_key = "production"
```

`COOLDECK_CONFIG_DIR`

moves the whole directory - handy for keeping experiments away from your real
setup. Full reference: [docs/configuration.md](/Resetnak/cooldeck/blob/main/docs/configuration.md).

Resolved in this order: `COOLDECK_TOKEN`

in the environment always wins, otherwise the instance's
`token_source`

decides.

| Source | Behaviour |
|---|---|
`keyring` |
OS keychain, written by `cooldeck setup` or `cooldeck auth add` - recommended |
`command` |
Runs an external command and reads the token from stdout (e.g. `["op", "read", "op://…"]` ) |
`env` |
Reads a named environment variable |
`plaintext` |
Token in the config file (mode `0600` ) - last resort |

```
cooldeck auth add production      # store a token in the keyring
cooldeck auth status production   # is one there? (never prints it)
COOLDECK_TOKEN=… cooldeck         # one-shot, nothing written anywhere
cooldeck                          the TUI dashboard
cooldeck --demo                   offline demo data, no Coolify needed
cooldeck --instance production    start on a specific instance
cooldeck --theme catppuccin       theme override for this run
cooldeck --debug                  structured debug log (redacted)

cooldeck mcp                      serve the instance to an agent over MCP (read-only)
cooldeck mcp --allow-mutations    ... and let it deploy, restart, start and stop

cooldeck setup                    first-run wizard: URL, token, keyring
cooldeck theme                    interactive theme picker with live preview
cooldeck auth add|status|delete <instance>
cooldeck config path|validate
cooldeck version                  version, commit, build date
```

**Tokens stay out of sight**: never rendered in the UI, never written to logs, toasts or the diagnostics export -enforces the log side, and the diagnostics dump is secret-free by construction.`internal/logging/redact.go`

**Log output is sanitised**: raw ANSI control sequences from a remote log stream cannot repaint your terminal.** Only**URLs are ever handed to the browser.`http`

/`https`

**Deleting an instance** removes the*local*config entry and its keyring item. It never touches anything in Coolify.**Permissions degrade gracefully**: Coolify has no permission-introspection endpoint, so CoolDeck assumes full capabilities and switches individual features off on a`403`

- showing them disabled with a reason rather than hiding them.**What it does not protect you from**: the config file is written`0600`

but a`plaintext`

token source still puts the token on disk;`insecure_skip_verify = true`

really does disable TLS verification for that instance; and an MCP client started with`--allow-mutations`

can deploy, restart, start and stop without a confirmation, because an agent has nobody to ask. All three are opt-in, and all three are worth a second thought.

Reporting a vulnerability: [SECURITY.md](/Resetnak/cooldeck/blob/main/SECURITY.md).

Layered so that the same use cases back the TUI, a CLI subcommand and the MCP server:

```
cmd/cooldeck → internal/cli        cobra, flags, config, service construction
             → internal/tui        Bubble Tea model + views (presentation only, no I/O)
             → internal/mcpserver  MCP tools over stdio (no HTTP client, no TUI imports)
             → internal/app        Service interface = the use cases
               ├── app/demo        deterministic fake service (demo mode + golden tests)
               └── coolify         HTTP client + DTO → domain mapping
             → internal/domain, config, credentials, logging, platform, version
```

Built on [Bubble Tea / Charm v2](https://github.com/charmbracelet/bubbletea). Every TUI screen is
covered by golden snapshots rendered from the demo service, so a layout regression fails CI instead of
shipping.

| Doc | |
|---|---|
|

[Decisions](/Resetnak/cooldeck/blob/main/docs/decisions)[Coolify API](/Resetnak/cooldeck/blob/main/docs/coolify-api.md)[MCP server](/Resetnak/cooldeck/blob/main/docs/mcp.md)[Configuration](/Resetnak/cooldeck/blob/main/docs/configuration.md)[Keybindings](/Resetnak/cooldeck/blob/main/docs/keybindings.md)[Troubleshooting](/Resetnak/cooldeck/blob/main/docs/troubleshooting.md)[Changelog](/Resetnak/cooldeck/blob/main/CHANGELOG.md)

```
make check               # fmt-check + vet + lint + test + build - run this before every PR
make run                 # go run ./cmd/cooldeck --demo
make test-race
make test-update-golden  # refresh the TUI snapshots, then *read the diff*
make lint                # staticcheck + golangci-lint (same versions as CI)
make bench               # view rendering benchmarks
make vuln                # govulncheck
```

The four GIFs in this README are generated, not hand-recorded: `vhs cassette.tape`

, `vhs tail.tape`

,
`vhs mcp.tape`

and `vhs outage.tape`

rebuild the binary and re-record against `--demo`

, so they
cannot drift from the working tree. All of them run in a throwaway `COOLDECK_CONFIG_DIR`

under
`/tmp`

and touch nothing of yours.

**Shipped:** applications dashboard, detail with logs, confirmed mutations, deployments queue,
deployment outcome notifications, multi-instance management, diagnostics, eight themes, demo mode,
an MCP server on the same `app.Service`

, golden tests, multi-OS CI.

**Next:** richer in-TUI token sources beyond the keyring, optional read-only views for services,
databases and servers, and a non-interactive CLI for scripts and CI.

Bug reports, feature requests and PRs are welcome - see [CONTRIBUTING.md](/Resetnak/cooldeck/blob/main/CONTRIBUTING.md) for the
local setup, the golden-test workflow and what `make check`

expects before review. Participation is
covered by the [Code of Conduct](/Resetnak/cooldeck/blob/main/CODE_OF_CONDUCT.md).

[MIT](/Resetnak/cooldeck/blob/main/LICENSE) © 2026 Alexandr Rešetňak
