Skip to Content

Container Manager

The Container Manager is the Server Mode control panel for every Docker container on your node. It replaces the trip to a terminal for docker run, docker exec, and docker-compose up with a focused UI that still routes every privileged action through the audited sysctl whitelist so you always have a trail of who did what.

Where Installed Apps manages catalogue-driven installs, Container Manager is for everything else: ad-hoc containers, custom compose stacks, private-registry images, and the quick exec into a running shell.

How It Works

  • Read-only views (list, inspect, image library) talk to the Docker daemon via the official SDK over /var/run/docker.sock.
  • Mutating actions (start / stop / restart / rm / run) go through internal/sysctl so every click lands in the sysctl_audit table. The daemon socket itself is never exposed to the browser.
  • Container exec opens a WebSocket to /ws/containers/{id}/exec that pipes a real docker exec -it PTY through xterm.js.
  • Image registries — private-registry credentials live in the image_registries table. Passwords are encrypted at rest with AES-256-GCM via the shared internal/crypto/secrets helper; the host key lives in a 0600 file outside the database so a DB dump alone isn’t enough to recover them.

Tabs

The page is split into three tabs that share the same header button Run container so creating something new is always one click away:

Containers

A live table of every container on the host with:

  • Status pill (running / paused / exited / restarting).
  • Mini CPU + memory sparkline per running row, fed by the existing /api/docker/containers/{id}/stats/stream SSE endpoint — the same stream that powers the Dashboard Docker widget, so no extra daemon load.
  • Port mappings (host:container/proto) and Quazzar-managed badge so you can tell at a glance which rows the App Store owns.
  • Action menu: Restart, Inspect, Exec shell, Remove (with confirmation).

Compose

Paste a docker-compose.yml into the textarea or drop a file onto the dropzone. Cloud OS parses the YAML server-side, shows a preview card per service (image, ports, volumes, env-var keys, restart policy) and any warnings about unsupported fields (e.g. build: without image: is skipped with a warning rather than failing the whole import). Click Deploy stack to pull + run each service in alphabetical order.

The importer understands a pragmatic subset of compose v3:

  • image, environment (both map + list form), ports, volumes, restart, command, labels, networks.
  • Unsupported today: build:, configs:, secrets:, deploy:. They’re dropped with a warning rather than failing silently.

Images

A one-screen view of image inventory, prune, and registry credentials:

  • Pull any image by reference (nginx:1.25, ghcr.io/org/app:latest, …). If a matching private registry is configured, the pull auto-logs-in first.
  • Prune dangling one-click cleanup reports reclaimed bytes.
  • Delete individual images; rows already used by a container are disabled.
  • Private registries — add credentials for ghcr.io, registry.gitlab.com, or a self-hosted registry. The password never echoes back on list.

Exec Shell

The exec terminal opens a modal with xterm.js hooked to a WebSocket. The wire protocol is identical to the Web Terminal:

  • Binary frames carry raw PTY bytes in both directions.
  • Text frames are JSON control messages — currently only {"type":"resize","cols":<n>,"rows":<n>}.

The default command is /bin/sh — containers that include bash can request it via a ?cmd=/bin/bash query param (the terminal falls back automatically if the requested binary isn’t present).

Security Model

Every mutating click funnels through sysctl.Service.Dispatch with one of the whitelisted verbs (docker.start, docker.stop, docker.restart, docker.rm). The audit table records user, action, target container ID, exit code, duration, and truncated stdout/stderr per call — so an operator reviewing the audit log can see exactly which container was stopped and by whom. Registry credentials never leave the server; when a pull needs them, the backend issues a docker-login on your behalf and the password stays encrypted in the DB.

API Reference

EndpointDescription
GET /api/containersList every container on the host.
GET /api/containers/{id}Detailed inspect (env, mounts, networks, cmd).
POST /api/containers/{id}/{start|stop|restart}Lifecycle actions.
DELETE /api/containers/{id}Force-remove a container.
POST /api/containers/runCreate + start from a {image, name?, env, ports[], volumes[], restart_policy} body.
POST /api/containers/compose/previewParse a compose YAML, return services + warnings.
POST /api/containers/compose/deployDeploy a parsed compose stack.
GET /api/containers/imagesList images with size, age, used-by count.
DELETE /api/containers/images/{id}Delete an image.
POST /api/containers/images/prunePrune dangling images.
POST /api/containers/images/pullPull an image (uses stored registry creds if applicable).
GET /api/containers/registriesList configured registries (no passwords).
POST /api/containers/registriesAdd / update a registry credential.
DELETE /api/containers/registries/{id}Delete a registry credential.
GET /ws/containers/{id}/execWebSocket exec endpoint.

All routes are session-authenticated — a browser cookie is required.