Kata (型) means form / model / pattern. This project provides a tiny “micro-PaaS” wrapper around Docker (Compose or Swarm) with implicit HTTP routing via Traefik.
- Parses an application
kata-compose.yamland generates a.docker-compose.yamlused for deployment - Supports either Docker Swarm (
docker stack deploy) or Docker Compose (docker compose up -d) per app (auto‑selects, overridable) - Implicitly wires HTTP routing through Traefik using generated labels
- Builds lightweight runtime images on‑demand for
runtime: python,runtime: nodejs,runtime: php,runtime: bun, or static assets. - Manages per‑app bind‑mounted directories (code, data, config, venv, logs, repos)
- Merges environment variables from multiple sources and injects them into each service
- Provides simple git push deployment hooks (
git-receive-pack/git-hook) - Offers helper commands for secrets (Swarm only), mode switching, and Traefik inspection
Mandatory:
- Docker 20.10+ (Swarm optional; if inactive, Compose mode is used)
- Python 3.12+ (to run
kata.py)
Optional (HTTP routing via Traefik):
- Traefik v3 on the host (containerized). Kata will create or reuse the external Docker network
traefik-proxyand the volumetraefik-acmefor certificate storage, and will start a shared Traefik container (kata-traefik) if none is running.
systemd / Podman are not required by the current code path (earlier design notes referenced them).
Tested on Debian 12/13, recent Ubuntu, and macOS with Colima/Docker Desktop.
Traefik is opt-in. If you do not provide a traefik: block, Kata will not add labels or inject Traefik. Add a traefik block to the root of your kata-compose.yaml to enable routing.
You can always add Traefik labels manually on any service; the traefik block is just a convenience that generates a consistent set of labels for one target service.
Key defaults (when traefik is provided):
- Router name:
<app> - Host rule:
traefik.host(required) - Entry points:
websecureby default;web→websecureredirect only if you setenable_http_redirect - Service:
traefik.service(defaults to the first service listed if omitted); declareports/exposeand settraefik.portif it differs from the declared port (defaults to 8000 if unset) - TLS: enabled by default when using
websecure; certificates stored in the external volumetraefik-acme - Network: only the Traefik-targeted service is attached to the external Docker network
traefik-proxy; other services are untouched
Kata can start or reuse a shared Traefik container named kata-traefik on the traefik-proxy network with the traefik-acme volume. If you already run Traefik, keep it attached to that network/volume and Kata will reuse it.
You can override host/entrypoint/redirect/port via labels under your service (Compose syntax), e.g.:
services:
web:
labels:
traefik.http.routers.websecure.rule: Host(`app.example.com`)
traefik.http.routers.websecure.entrypoints: websecure
traefik.http.services.web.loadbalancer.server.port: "5000"If a service does not declare ports or expose, Traefik will not be able to reach it. This is fine for workers/cron jobs:
services:
worker:
image: busybox
command: ["sh", "-c", "echo worker started; sleep infinity"]
# No ports/expose → not routable via Traefikkata config:traefik <app> [--json]— render generated labels and router/service ruleskata traefik:ls— list routers/services for the generated stackkata traefik:inspect <app>— show labels per service
Use runtime: static to serve a directory via BusyBox httpd (image kata/static). Defaults: PORT=8000, DOCROOT=/app.
Paths are rooted at KATA_ROOT (default: $HOME). The current directory names (note: singular app/) are:
- Code:
$KATA_ROOT/app/<app> - Data:
$KATA_ROOT/data/<app> - Config:
$KATA_ROOT/config/<app>(placeENVor.envhere to override variables) - Virtual env / runtime state:
$KATA_ROOT/envs/<app> - Logs:
$KATA_ROOT/logs/<app> - Git bare repos:
$KATA_ROOT/repos/<app>
Generated file: .docker-compose.yaml inside the app code directory (regenerated each deploy).
Add only the labels you need; Kata supplies sensible defaults.
services:
web:
labels:
traefik.http.routers.websecure.rule: Host(`app.example.com`)
traefik.http.routers.websecure.entrypoints: websecure
traefik.http.routers.websecure.tls: "true"
traefik.http.middlewares.web-redirect.redirectscheme.scheme: https
traefik.http.routers.websecure.middlewares: web-redirect
traefik.http.services.web.loadbalancer.server.port: "5000"Merged from (later sources override earlier):
- Base:
PUID,PGID, and per‑app root paths (APP_ROOT,DATA_ROOT,ENV_ROOT,CONFIG_ROOT,GIT_ROOT,LOG_ROOT) - Top‑level
environment:mapping inkata-compose.yaml(optional) ENVor.envfile in the app’s config directory- Service‑level
environmententries
Compose list form (["KEY=VALUE", "BARE_KEY"]) is normalized; bare keys default to empty string.
Recommended to set:
PORT(service listen port, especially for reverse proxying)BIND_ADDRESS(default127.0.0.1if omitted in your own config logic)DOMAIN_NAME(for host matchers / TLS)
Declare ports or expose on the service you want Traefik to reach; Kata no longer adds these automatically. If you prefer a different target, set traefik.service (and traefik.port if it differs from the declared port).
Automatically injected into each service unless already set: the base variables above.
For more details on Traefik labels, see the Traefik reference.
- Inspect generated config for an app:
kata config:traefik <app> --json- List routers/services for your stack:
kata traefik:ls- Typical errors:
- “PORT not set”: ensure your service declares
ports/exposeand the app listens there (or settraefik.http.services.<name>.loadbalancer.server.port; Kata assumes 8000 if nothing is specified). - Traefik not running: start it or allow Kata to inject the minimal service; ensure the
traefik-proxynetwork exists. - DNS/TLS: verify your host rule matches a reachable domain and that ports 80/443 are open to the world.
- “PORT not set”: ensure your service declares
Each example declares a PORT and exposes it on the web service so Traefik can route to it. By default the first service (web) is targeted; set traefik.service/traefik.port if you need a different mapping.
- docs/examples/minimal-python — FastAPI on the Python runtime
- docs/examples/minimal-nodejs — Express on the Node.js runtime
- docs/examples/minimal-php — Built-in PHP server on the PHP runtime
- docs/examples/minimal-bun — Bun server on the Bun runtime
- docs/examples/static-site — BusyBox httpd with the static runtime
- docs/examples/generic-whoami — Stock image path without runtime helpers
Default per host state:
- Swarm active → deploy via
docker stack deploy - Swarm inactive → deploy via
docker compose up -d
Override per app:
x-kata-mode: compose # or swarmOr with CLI:
kata mode <app> # show
kata mode <app> compose # set & restart
kata mode <app> swarmHelper file .kata-mode in the app root persists the selection.
If a service defines:
services:
web:
runtime: python # or nodejs | php | bun | static
command: ["python", "-m", "app"]Kata will build (once) or reuse a kata/<runtime> image from an internal Dockerfile, bind‑mount app/config/data/venv, and run runtime-specific prep:
- python: create venv +
pip install -r requirements.txt - nodejs:
npm install - php:
composer install --no-dev --optimize-autoloader - bun:
bun install - static: no prep; use BusyBox httpd
If you supply image: yourself, no runtime automation runs.
Commands:
kata secrets:set NAME=VALUE # NAME=@file, NAME=- (stdin), or just NAME (prompt)
kata secrets:ls
kata secrets:rm NAMEThey are disabled (with a warning) when Swarm is inactive.
Two internal commands (git-receive-pack / git-upload-pack) plus the git-hook are used when you push to a bare repo under $KATA_ROOT/repos/<app>. The post‑receive hook triggers git-hook which runs do_deploy.
You can also manually trigger deployment by piping a synthetic ref update:
echo "0000000000000000000000000000000000000000 $(git rev-parse HEAD) refs/heads/main" | kata git-hook <app>Selected commands (run kata help for full output):
| Command | Purpose |
|---|---|
| setup | Create root directories |
| ls | List apps & running state |
| restart / stop / rm | Lifecycle management |
| mode | Get/set deploy mode |
| config:stack | Show original kata-compose.yaml |
| config:docker | Show generated .docker-compose.yaml |
| config:traefik | Show generated Traefik labels/config |
| traefik:ls / traefik:inspect | Inspect routers/services and labels |
| secrets:* | Manage Swarm secrets |
| docker ... | Passthrough to docker |
| docker:services / ps | Inspect Swarm/Compose processes |
| run <cmd...> | Exec into a running container |
| update | (WIP) self‑update script |
Feedback / issues welcome. This README tracks the current code in kata.py; if something here is missing in code, file a bug.
