Recovery codes for 2FA, an API tokens page, and more of what Coolify and Dokploy offer per service: file mounts, a healthcheck command, Docker labels, Git LFS and searchable runtime logs.
Upgrade notes
- Nothing changes until someone turns it on. Every new setting is off or empty on an upgraded panel; node agents need no update (a node on an older agent returns the last 300 runtime-log lines whatever is asked).
- Five additive migrations.
0081_recovery_codesadds the emptyuser_recovery_codestable. Accounts with 2FA sign in exactly as before; they have no recovery codes until they create a set under Settings → Account.0082_service_filesadds the emptyservice_filestable, so every container starts with exactly the mounts it had.0083_health_commandadds the nullableservices.health_command; every service keeps its healthcheck.0084_docker_labelsadds the nullableservices.docker_labels; containers keep exactly the labels they had.0085_git_lfsaddsservices.git_lfs, off for every service.
Added
- Two-factor recovery codes: turning 2FA on hands out ten single-use codes, shown once to copy or download. At sign-in, one stands in for the authenticator code (web and CLI). Settings → Account shows how many are left and creates a new set (password and a current code). The help text already promised them; the panel had none.
- Search and download for runtime logs (Service → Runtime logs), like Coolify's and Dokploy's log viewers: filter to the lines that contain a text (case-insensitive, matches highlighted), load the last 300, 1000 or 5000 lines, pause the live refresh, and download what is loaded. API
GET /v1/services/:id/logs?tail=N(1 to 5000, default 300 as before); SDKservices.logs(id, { tail }). A node on an agent older than 0.16.3 returns the last 300 lines. - CLI:
ninedeploy services logs <id> --tail N --grep text. - File mounts (Service → Volumes → Files), like Coolify's and Dokploy's: text files (an nginx.conf, a config.yaml) kept encrypted by the panel and mounted read-only at a path in the container; up to 20 per service, 256 KiB each, applied on the next deploy. Docker services on the panel host; a node, Swarm or compose service with files is refused rather than started without them. API
/v1/services/:id/files; SDKservices.files. - Healthcheck command (Service → Settings → Healthcheck → Command inside the container): the deploy runs a command such as
pg_isreadyorcurl -fsS localhost:3000/upwithsh -cin the new container until it exits 0, logging failed attempts. Docker services on the panel host; APIhealthCheck: 'command'withhealthCommand. - Docker labels (Service → Settings → Docker labels): your own
key=valuelabels on the container and its replicas, for host tools that read them;ninedeploy.*,com.docker.*andtraefik.*are reserved. Docker services on the panel host; APIdockerLabels. - Git LFS (Service → Settings → Git LFS, off by default): each deploy replaces LFS pointer files with their content, fetched from the repository's own HTTPS LFS server with the service's credential;
.lfsconfigis ignored, every download and redirect passes the egress check, and each object is verified by SHA-256 and size. Builds on the panel host; APIgitLfs. - API tokens page (Settings → Security → API tokens): create a token for the CLI, CI or an MCP client with a name, an access level (read only, read & write, or full access for operators) and a lifetime; it is shown once. The list shows each token's access, last use and expiry, flags legacy unrestricted and expired tokens, and revokes after a confirmation. The API existed; the docs pointed to this page before it did.
Changed
- The top bar is two rows: the page title and the actions (New, search, activity, help) above, the workspace / project / label filters below, so the filters no longer push the actions off the bar.
Fixed
ninedeploy services logsprinted the oldest 50 of the last 300 lines. It now prints the newest lines.