Skip to main content

Dokli

Python package PyPI Version

A magical CLI/TUI for interacting with Dokploy.

                                                                    █
                                                                   ████
                                                                    ███████            █
               █████████████████████████                             ████████   ████████
             ███████████████████████████████                          ██████████████████
            ████                        █████████                       ██████████████
            ████          ███               █████████                      ████
            ███           ███                   ██████████               █████
            ███                                      ████████████████████████
            ████████████████                              █████████████████
            ██████████████████████                               ███████
            ████            ██████████                     ██████████          ██████
             ██████               ███████████████████████████████          ████████
               ████████                ████████████████████             ████████
             █     █████████                                        ████████     ███
            █████      █████████                                ████████      ██████
            ████████       ███████████                    ███████████      █████████
            ████ ██████         ████████████████████████████████        ███████ ████
            ████   ███████            ████████████████████           ███████    ████
             █████    ████████                                   ████████    ██████
              ███████     ████████                           █████████     ███████
              █████████      ████████████              ███████████      ██████████
               ████ ██████        ████████████████████████████       ███████ ████
                ████   ██████            ██████████████            ██████   ████
                 █████   ███████                               ████████   █████
                   █████    █████████                      █████████    █████
                    ██████      ████████████████████████████████      ██████
                      ██████         ██████████████████████         ██████
                        ███████                                  ██████
                           ████████                          ████████
                              ███████████               ██████████
                                   ██████████████████████████
                                          ████████████

Installation

pip install dokli
# with TUI support
pip install "dokli[tui]"

# with uv tool
uv tool install dokli
# with TUI support from UV
uv tool install "dokli[tui]"

# latest from git
pip install git+https://github.com/jonykalavera/dokli.git
# with TUI support from git
pip install git+https://github.com/jonykalavera/dokli.git#egg=dokli[tui]

Tested with Dokploy versions:

  • 0.29.13

Configuration

Create the configuration file at ~/.config/dokli/dokli.yaml. Example:

connections:
  - name: test-env
    url: https://test.example.com
    api_key: ****************************************
    notes: "Our test environment. Handle with care!"
  - name: prod-env
    url: https://prod.example.com
    api_key_cmd: "secret-tool lookup dokli prodEnvApikey"
    notes: "Our prod environment. Handle with even more care!"

You can use api_key_cmd to load the API key from a command such as secret-tool instead of entering it in the config file. This is highly recommended for security reasons.

Configuration uses pydantic-settings which means it can also be set via environment variables using the DOKLI_ prefix.

You can also manage connections with dokli connections ls|add|update|remove|get|test, or from the TUI.

dokli use <name> sets the default connection — commands that take an optional connection name then use it automatically (e.g. dokli stats, dokli logs, dokli state, dokli tui).

Secrets in the system keychain

API keys, git provider credentials and database passwords can be stored in your OS keychain instead of the YAML files:

  • dokli secrets set|get|rm <account> — manage keychain entries (accounts like conn.<name>, provider.<name>, db.<name>); get masks by default, --show prints in plain text.
  • dokli connections add <name> --keyring / dokli connections update <name> --keyring — store the API key in the keychain (api_key_keyring: true) so it never touches the config file.
  • In manifests, token_keyring: true (git providers) and password_keyring: true (databases) resolve those secrets from the keychain at apply time. Generic resources: fields resolve secrets with {"keyring": "account"} or {"cmd": "..."} in data.
  • Resolution order is: literal value → keychain → *_cmd.

Uses the cross-platform keyring package (SecretService / macOS Keychain / Windows Credential Locker).

CLI

Features

  • Commands are inferred from the OpenAPI spec, which allows:
    • support for multiple Dokploy API versions.
    • support for all API entities actions/verbs.
  • magical JSON parameters %json:{"projectId": "daspdoada798sda"}
  • magical file parameters %file:/path/to/data/foo.redis.json
  • shell completion for configured connection names (dokli state <TAB>, dokli connections get <TAB>, ...)
  • dokli ls [<conn>] [--type TYPE] [--search TERM] [--format agent] [--fields a,b] — flat list of every service and its id (project/environment/type/name/appName), handy for scripting and agents; --type filters by service type, --search by name.
  • --format agent (NDJSON dataframe) is available on api, stats, ls and state: line 1 is the column names, every following line is one record's values (arrays). Nested objects flatten with __. json.load-parseable per line — made for agents and scripting. --fields a,b,c keeps only the given top-level fields (jq-like), on api (any format), stats/ls (agent) and state (agent or yaml).
  • dokli logs <conn> (--compose-id + --container-id | --application-id | --deployment-id) [-n N] [-f] — show a service's last -n log lines (REST, one-shot) or stream live with -f (WebSocket; deployment -f streams the whole log).
  • dokli terminal <conn> (--container-id X | --server-id S) [--shell sh|bash|zsh|ash] [--username USER] [--port N] — an interactive shell into a container (via /docker-container-terminal) or the host over SSH (/terminal). Takes over the current TTY (raw mode, resize-aware via SIGWINCH); exit with exit/Ctrl+D.
  • Stable error channel — exit codes are part of the contract: 0 success, 1 runtime/API error, 2 usage error. Errors always go to stderr; when a machine format was requested (--format json or agent) the failure is a JSON object {"error": ..., "exit_code": N} on stderr so callers can parse it (otherwise a plain red line).
  • dokli doctor [<conn>] [--format python|json|agent] — quick instance health: connectivity (web root reachable), auth (settings.health with the resolved api key) and schema cache freshness (~/.config/dokli/cache/<conn>.openapi.json, stale after 7 days — run dokli refresh). Exits 0 when all checks pass, 1 otherwise; no <conn> uses the default connection.
  • dokli stats [<conn>] [(--compose-id | --application-id | --container-name | --container-id)] [--app-name NAME] [--app-type TYPE] [--height 1-8] [--samples N] [--no-backfill] [--once] [--format agent] — stream stats live over WebSocket as braille sparklines (CPU, memory, network, block I/O, disk). With no selector it streams the host system stats (dokploy/application); a service selector targets that service, and --container-name/--container-id target a container directly by its docker name. --once prints a single snapshot and exits (handy for scripting). --format agent emits an NDJSON dataframe (header row once, then one row per sample) for agents/scripts. Network and block I/O are plotted as cumulative totals (flat line when idle), matching the Dokploy web UI; disk is only shown for system stats. Charts are backfilled from the REST monitoring history when available (the Dokploy API only returns it for system stats — dokploy — so services/containers start empty and fill live; skip with --no-backfill), and the header shows the sample time range (e.g. … · 13:32:04→13:38:12). --samples defaults to the console width so the chart fills the screen; --height tunes the chart rows.
  • output formats:
    • yaml
    • json
    • python
    • table (experimental)

Usage

$ dokli

 Usage: dokli [OPTIONS] COMMAND [ARGS]...

 Magical Dokploy CLI/TUI.


╭─ Options ───────────────────────────────────────────────────────────────────────────╮
│ --install-completion          Install completion for the current shell.             │
│ --show-completion             Show completion for the current shell, to copy it or  │
│                               customize the installation.                           │
│ --help                        Show this message and exit.                           │
╰─────────────────────────────────────────────────────────────────────────────────────╯
╭─ Commands ──────────────────────────────────────────────────────────────────────────╮
│ tui      Text User Interface.                                                       │
│ init     Scaffold a new dokli manifest.                                             │
│ refresh  Refetch and refresh the cached OpenAPI schema for a connection.            │
│ state    Show the current state of a Dokploy instance.                              │
│ plan     Show what would change between the manifest and the live instance.         │
│ apply    Apply the manifest to a Dokploy instance (idempotent, additive).           │
│ validate Validate the manifest offline against the connection's schema.             │
│ export   Export the live state of an instance into a manifest.                      │
│ api      API commands                                                               │
│ connections  Manage connections.                                                    │
╰─────────────────────────────────────────────────────────────────────────────────────╯

$ dokli api test-env project all
- organizationId: ysHDHlhX4a3zOG2fLsske
  applications: []
  compose: []
  createdAt: '2024-08-05T02:45:38.168Z'
  description: null
  mariadb: []
  mongo: []
  mysql: []
  name: Dokli
  postgres: []
  projectId: zuanf1SWHMFO11y6xqpRR
  redis: []

$ dokli api test-env project create --body '%json:{"name": "Dokli"}' --format table
               API Response
┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Key                 Value                    ┃
┡━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ projectId           zuanf1SWHMFO11y6xqpRR    │
│ name                Dokli                    │
│ description         None                     │
│ createdAt           2024-08-05T02:45:38.168Z │
│ organizationId      ysHDHlhX4a3zOG2fLsske    │
└────────────────────┴──────────────────────────┘

$ dokli api test-env project one --format json zuanf1SWHMFO11y6xqpRR
{"projectId": "zuanf1SWHMFO11y6xqpRR", "name": "Dokli", "description": null,
"createdAt": "2024-08-05T02:45:38.168Z", "organizationId": "ysHDHlhX4a3zOG2fLsske",
"applications": [], "mariadb": [], "mongo": [], "mysql": [], "postgres": [],
"redis": [], "compose": []}

JSON output stays parseable when piped (newlines in fields stay escaped), and --indent N (default 0 = compact, JSON only) pretty-prints.

Dokli as Code

Dokli can manage a Dokploy instance declaratively, like Docker Compose for Dokploy. A manifest file (dokploy.yaml) describes the desired state and dokli apply brings the instance to match it — idempotent and additive (it never deletes resources that are not in the manifest, unless you pass --prune).

Commands

Command Description
dokli init Scaffold a new manifest.
dokli refresh [connection] Refetch and refresh the cached OpenAPI schema for a connection.
dokli state [connection] Show the current state of an instance.
dokli plan [-f dokploy.yaml] [--prune] Preview what would change. -f also accepts a directory (every *.yaml/*.yml in it). --prune also plans deletions.
dokli apply [-f dokploy.yaml] [--dry-run] [--deploy] [--prune] Configure the instance to match the manifest. --dry-run only previews; --deploy also triggers deployments.
dokli validate [-f dokploy.yaml] Validate the manifest offline against the connection's schema.
dokli export [connection] [-o file] [--include-secrets] Reverse-engineer a live instance into a manifest, including generic resources:.

--prune (destructive): deletes child records and services that exist in the instance but are not described in the manifest — scoped to projects declared in the manifest; projects and environments absent from it are never touched. Always preview with dokli plan --prune first.

Manifest

# dokploy.yaml
apiVersion: v1
connection: prod

git_providers:
  - name: github
    provider: github
    token_cmd: "secret-tool lookup dokli github"

projects:
  - name: myapp
    services:
      - type: compose
        name: backend
        source:
          provider: github
          repository: <owner>/backend
          branch: main
        compose_path: docker-compose.yml
      - type: application
        name: web
        image: nginx:latest
        env: |
          NODE_ENV=production

resources:
  - kind: domain
    name: www.example.com
    in: compose:backend
    data:
      host: www.example.com
      https: true
  - kind: port
    name: 8080
    in: application:web
    data:
      publishedPort: 8080
      targetPort: 80
      protocol: tcp
  • apiVersion is the Dokli manifest format version (only v1 today); dokployVersion (the Dokploy API version the manifest was written against) is stamped by init/export.
  • Services live in the project's default environment (Dokploy creates one per project).
  • compose_file accepts raw compose YAML or a path to a local file (mutually exclusive with source).
  • Secrets are never stored in the manifest. Git provider credentials are write-only in Dokploy's API; reference them with token_cmd (same pattern as api_key_cmd). export redacts service environment variables by default (--include-secrets to include them) and reports which providers need credentials.

*_cmd references (api_key_cmd, token_cmd, password_cmd) run through a shell, so they can resolve secrets from tools like Ansible Vault:

# dokploy.yaml
projects:
  - name: myapp
    services:
      - type: postgres
        name: db
        password_cmd: "ansible-vault view --vault-password-file ~/.vault-pass secrets/vault.yml | yq -r '.db_password'"

Generic resources

projects: describes the services themselves (the typed backbone: state, plan/apply, deploy). resources: describes the leaf records that hang off those services — domains, ports, redirects, security, schedules, backups, mounts. They are resolved against the connection's OpenAPI document, so any field (or future entity) is supported without a code change.

A resource has kind, name, in (the parent path) and data (the create/update fields; parent ids are derived from in, never repeated in data):

resources:
  - kind: domain
    name: www.example.com
    in: project:myapp / environment:production / compose:backend
    data:
      host: www.example.com
      https: true
  • in is a <kind>:<name> path to the parent service. Ancestor segments (project:, environment:) scope the lookup and disambiguate same-named services across projects. in: compose:backend alone matches globally.
  • Matching is string-based on a per-kind match key, falling back to name: domain → host, port → publishedPort, redirects → regex, security → username, mount → filePath, backup → schedule.
  • Parent restrictions: port/security/redirects hang off application services only; domain off application/compose; mount off any service. dokli validate flags violations.
  • Secrets in data use the same dict references as elsewhere: {"cmd": "..."} (run through a shell) or {"keyring": "account"} (from your OS keychain):
resources:
  - kind: security
    name: admin
    in: application:web
    data:
      username: admin
      password: {keyring: app-web-admin}

apply is additive here too: an existing resource is updated, a missing one is created, and nothing is ever deleted because it is absent from the manifest. dokli export emits the instance's existing child records as resources: entries (match key included, ids and secrets omitted; backup destinations by name), so the manifest round-trips: export → edit → apply.

Workflow

dokli export <conn> -o dokploy.yaml   # capture an existing instance
dokli plan                           # preview changes
dokli apply --dry-run                # dry run
dokli apply                          # configure the instance

-f accepts a directory: apply/plan/validate then process every *.yaml/*.yml in it (sorted), one report per manifest.

TUI

A schema-driven TUI (dokli tui) that generates its interface from the Dokploy OpenAPI document — no hand-written screens per entity, so it adapts to any API version.

  • Yazi-style 3-column browser: parent | current | detail, with j/k navigate, h/l drill in/out, / filter, F5 refresh, y yank the selected id. Common actions get a curated key (c create, u update, x deploy, L logs, d deployments, s start, o stop, ...); everything else runs via the action picker or palette.
  • Command palette (ctrl+p): search across commands and the focused screen's available actions, with shortcuts shown in the help line.
  • Action picker (f4): the palette pre-filtered to the selected record's actions — reach any verb that has no direct key.
  • Help (?): lists the app, screen and contextual keybindings.
  • Related actions: entities whose list action needs a parent (e.g. deployment.allByCompose) are surfaced as a separate contextual action on the parent record (Deployments on a compose/application/server, d), opening a navigable list; entities whose all requires a parent are listed at the top level via their canonical no-param action (e.g. deployment.allCentralized).
  • Children by category: drilling into a record shows an intermediate level of child categories — e.g. Containers, Deployments, Domains (N), Mounts (N), Ports (N) — with free counts for nested arrays and lazy loading for containers/deployments. Selecting a category lists its records; a single-category record drills straight through, and environments (a per-project filter) go directly to their services.
  • Containers: selecting a container exposes docker actions (restart/stop/start/...) and the parent service's logs (compose.readLogs/application.readLogs with the container id); swarm-aware for stack composes. delete/remove actions are bound to the Delete key, deploy to x.
  • Loading feedback: a splash screen with the Dokploy logo shows while a connection's schema is fetched (off the event loop; escape cancels it), and loading spinners appear while the browser refreshes entities and results re-fetch. API calls run off the event loop and time out quickly, so an unreachable instance never freezes the UI; a live connectivity check warns when the instance is unreachable but a cached schema was used.
  • Results: read-only queries open a result screen with search (/, n/N to jump) and F5 to re-fetch — handy for logs. Logs follow by default: container and deployment logs stream live over WebSocket (falling back to the polling API when the connection or auth fails).
  • Stats: S opens live stats for the host system (or the active connection's system); selecting a compose/application/container in the browser and pressing S shows that service's stats. The live stats run the dokli stats CLI on a pty and re-render its ANSI frame — the screen shows the exact dokli stats ... command so you can take it to a terminal.
  • Terminal: T opens an interactive shell into the selected container (or a compose/application service — picking a container when several run). Keystrokes pass through to the dokli terminal CLI on a pty (which bridges to Dokploy's terminal WebSocket), with resize and live ANSI rendering; the hint line shows the exact dokli terminal ... command. Exit the session with exit/Ctrl+D.
  • Forms: foreign-key id fields (serverId, destinationId, registryId, certificateId, sshKeyId, git provider ids, environmentId scoped to the current project, service/db parents like composeId/postgresId enumerated across the project tree, and mounts.create's serviceId driven by serviceType) render as a dropdown of live candidates instead of a raw id; when the source is empty or unreachable they fall back to free text. Parent-id fields of child records are hidden and injected from the navigation context. Update forms (and the wizard, which skips the hidden steps) hide provider-specific fields until a switch value is chosen (e.g. sourceType=raw shows the inline composeFile and hides the github/bitbucket/gitlab/gitea fields — git sources show their repo path instead; applications also gate build fields on buildType, which is itself hidden for sourceType=docker).
  • Entity icons are color-coded (Catppuccin palette), and container states show as a traffic-light dot.
  • Connections are managed from the TUI (add/edit/delete, persisted to the config file); dokli tui [connection] opens a specific connection directly.
  • Mask secret-like fields in forms and results.

Connections

Connections

Yazi-style browser

Browser

Compose detail

Compose detail

Command palette

Command palette

Result view (logs + search)

Result view

Live stats

Live stats

Splash

Splash

TUI customization

A tui: section in the config customizes the appearance and behavior (defaults are the built-in Catppuccin look, so nothing breaks):

tui:
  theme: dark            # or "light"
  colors:                # Textual ColorSystem field overrides (both variants)
    primary: "#89b4fa"
    background: "#1e1e2e"
  entity_colors:         # per-entity icon colors (compose, application, redis, ...)
    compose: "#a6e3a1"
    redis: "#fab387"
  state_colors:          # container-state traffic-light colors
    running: "#a6e3a1"
    exited: "#f38ba8"
  entity_order: [project]   # entities surfaced first in the browser list (default: project)
  keys:
    app:                 # app-level actions: toggle_dark, connections, help, quit, command_palette, pick_action, cancel
      connections: n
    verbs:               # extend/override the curated action verbs (see below)
      deploy: z
  auto_deploy: false     # deploy a service after a create/update from a form
  • colors accepts any ColorSystem field: primary, secondary, warning, error, success, accent, background, surface, panel, boost. Accent fields apply to both theme variants; structural fields (background, surface, panel) only affect the active theme: variant, so toggling light/dark (D) still switches the background.
  • App keys remap the global shortcuts (D dark, C connections, ? help, q quit, ctrl+p palette, f4 action picker, escape back); remapped keys are reserved so entity actions never clash with them.
  • entity_colors overrides the per-entity icon colors (compose, application, redis, project, ...); state_colors overrides the container-state traffic-light colors (running, paused, exited, dead, ...). Any entity/state not listed keeps its default.
  • entity_order lists the entities surfaced first in the top-level browser list (in order); the rest follow alphabetically. Empty defaults to project first.
  • keys.verbs extends or overrides the curated action verbs — only the frequent ones get a direct key (create/new c, update u, edit e, save w, start s, stop o, remove/delete delete, deploy x, redeploy X, testConnection t, restart R, readLogs L, rebuild b, move m, duplicate d, rollback Z). Verbs without a key — and related shortcuts like d (Deployments) and L (Logs) — are reachable via f4 / ctrl+p.
  • auto_deploy triggers the entity's deploy action after a successful create/update form (best effort — skipped when the record id is unknown).

Motivation

The CLI is designed to keep up with any changes in the API. Commands are dynamically inferred from the OpenAPI spec. I did this because I want to do some test automation and the official CLI seems incomplete at the moment. The TUI is because I am into tools like yazi, lazygit, k9s, dry, etc. I like to keep my terminal open at all times $. Also, it seemed to me like something cool to do this weekend. I learned a bunch about textual, typer and Dokploy.

Release

Releases are automated via GitHub Actions (.github/workflows/release.yml): pushing a v* tag builds the package with uv build, publishes it to PyPI via trusted publishing (OIDC), and creates a GitHub Release.

make release VERSION=0.2.0

This requires the repo to be configured as a trusted publisher on PyPI (no API token needed). The version in pyproject.toml must match the tag.

Buy me a 🌮

I'm Mexican, I prefer tacos. But ☕ is also nice. You can use the 🫶 sponsor button on the top.

Also pretty please and thanks in advance 🥺.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dokli-0.6.0.tar.gz (574.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dokli-0.6.0-py3-none-any.whl (152.8 kB view details)

Uploaded Python 3

File details

Details for the file dokli-0.6.0.tar.gz.

File metadata

  • Download URL: dokli-0.6.0.tar.gz
  • Upload date:
  • Size: 574.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for dokli-0.6.0.tar.gz
Algorithm Hash digest
SHA256 93e96396e0a569ed61e28f6d5e8370a8df1b194a87a5d25e17a891ee13f28e27
MD5 21f87440ca486a59c273accde693ac86
BLAKE2b-256 fde1c5ef91509c6082710436bc19ad9789958da345c087e99b4af430c4bd73f5

See more details on using hashes here.

Provenance

The following attestation bundles were made for dokli-0.6.0.tar.gz:

Publisher: release.yml on jonykalavera/dokli

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file dokli-0.6.0-py3-none-any.whl.

File metadata

  • Download URL: dokli-0.6.0-py3-none-any.whl
  • Upload date:
  • Size: 152.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for dokli-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 dd3b00c5dc328b3de675deab102c5287c895497bff3da149c05fc1608661ce3c
MD5 cc4c65a9c64f6ab1d51b53ba250da261
BLAKE2b-256 489ee4abf44172cba414af8fd8a32a78e1672886cc153ed3449a14eaa37334a6

See more details on using hashes here.

Provenance

The following attestation bundles were made for dokli-0.6.0-py3-none-any.whl:

Publisher: release.yml on jonykalavera/dokli

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.6.0 This release

2 files

0.5.0

2 files

0.4.1

2 files

0.4.0

2 files

0.3.0

2 files

0.2.0

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page