Dokku Architecture¶
This document provides a developer-focused overview of Dokku's internal architecture. It is intended for contributors who want to understand how Dokku works under the hood before diving into the codebase.
High-Level Architecture¶
Dokku is a Docker-powered Platform as a Service (PaaS) that provides a Heroku-like deployment experience. The following diagram shows the main components and their interactions:
+------------------+
| User/Client |
+--------+---------+
|
| SSH / git push
v
+--------+---------+
| dokku binary |
| (bash script) |
+--------+---------+
|
| parse_args / execute_dokku_cmd
v
+--------+---------+
| Plugin System |
| (plugn) |
+--------+---------+
|
| triggers / subcommands
v
+--------+---------+ +------------------+
| Core Plugins |---->| Docker / K8s |
| (apps, git, | | Runtime |
| config, etc.) | +------------------+
+------------------+
Key components:
- dokku binary: The main entry point (
/dokku), a bash script that handles authentication, argument parsing, and routes commands to plugins - Plugin System: Uses plugn to execute triggers and discover commands
- Core Plugins: Implement all Dokku functionality (apps, git, config, builders, schedulers, proxies, etc.)
- Runtime: Docker (default via
scheduler-docker-local) or Kubernetes (viascheduler-k3s)
Directory Structure¶
Source Tree¶
dokku/
├── dokku # Main CLI entry point (bash script)
├── plugins/ # All plugin source code
│ ├── apps/ # App management
│ ├── git/ # Git push handling
│ ├── config/ # Environment variables
│ ├── builder-*/ # Build backends (herokuish, pack, dockerfile, etc.)
│ ├── scheduler-*/ # Deployment schedulers (docker-local, k3s)
│ ├── *-vhosts/ # Proxy implementations (nginx, traefik, caddy, etc.)
│ └── common/ # Shared functions and utilities
├── docs/ # Documentation (markdown)
├── debian/ # Debian packaging files
├── contrib/ # Installation scripts and helpers
└── tests/ # Integration tests (bats)
Runtime Directories¶
When Dokku is installed, it creates the following directory structure:
/var/lib/dokku/
├── core-plugins/ # Core plugin binaries (installed from source)
│ ├── available/ # All available core plugins
│ └── enabled/ # Symlinks to enabled plugins
├── plugins/ # Community plugins
│ ├── available/
│ └── enabled/
└── data/ # Plugin data storage
└── <plugin>/ # Per-plugin data
└── <app>/ # Per-app properties
$DOKKU_ROOT (~dokku by default)
├── <app>/ # Per-app data
│ ├── refs/ # Git refs
│ ├── HEAD # Current git HEAD
│ ├── tls/ # SSL certificates
│ └── ... # Other app-specific files
└── .dokkurc/ # Global configuration overrides
Plugin Directory Layout¶
Each plugin follows a consistent structure:
plugins/<plugin-name>/
├── plugin.toml # Plugin metadata (description, version)
├── commands # Help output and catch-all command handler
├── subcommands/ # Individual command implementations
│ ├── default # Default command (e.g., `dokku apps`)
│ └── <command> # Named commands (e.g., `dokku apps:create`)
├── functions # Public functions for other plugins to source
├── internal-functions # Private functions
├── triggers.go # Go-based trigger implementations
├── *.go # Additional Go code
└── Makefile # Build configuration
Plugin System Architecture¶
Dokku's functionality is entirely implemented through plugins. This architecture provides:
- Extensibility: Add new features without modifying core code
- Loose coupling: Plugins communicate via well-defined triggers
- Composability: Mix and match builders, schedulers, and proxies
Plugin Communication via Triggers¶
Plugins communicate through the trigger system powered by plugn. When a trigger is fired, plugn executes matching scripts from all enabled plugins:
+---------------+ plugn trigger +---------------+
| Plugin A | -------------------> | Plugin B |
| (fires event) | "post-deploy" | (listens for |
+---------------+ | event) |
+---------------+
|
v
+---------------+
| Plugin C |
| (also listens)|
+---------------+
Trigger execution flow:
- A plugin calls
plugn trigger <trigger-name> [args...] - plugn searches
$PLUGIN_ENABLED_PATH/*/for files named<trigger-name> - Each matching executable is run with the provided arguments
- Triggers can return data via stdout or signal errors via exit codes
Key trigger categories:
| Category | Examples | Purpose |
|---|---|---|
| App lifecycle | post-create, pre-delete, post-deploy |
React to app events |
| Build | builder-detect, pre-build, builder-build |
Control build process |
| Deploy | scheduler-deploy, check-deploy |
Manage deployments |
| Proxy | proxy-build-config, nginx-pre-reload |
Configure reverse proxy |
| Git | git-pre-pull, receive-app |
Handle git operations |
For the complete list of triggers, see Plugin Triggers.
Calling Triggers from Go Code¶
Go-based plugins use the common.CallPlugnTrigger function:
result, err := common.CallPlugnTrigger(common.PlugnTriggerInput{
Trigger: "post-deploy",
Args: []string{appName, port, ip, imageTag},
})
Command Flow¶
When a user runs a command like dokku apps:list, here's what happens:
User runs: ssh dokku@host apps:list
|
v
+-------------------+-------------------+
| dokku bash script |
| 1. Source /etc/default/dokku |
| 2. Set DOKKU_ROOT, PLUGIN_PATH |
| 3. Source common/functions |
| 4. Call parse_args() |
| 5. Check user permissions |
+-------------------+-------------------+
|
v
+-------------------+-------------------+
| execute_dokku_cmd() |
| 1. Handle plugin aliases |
| 2. Check for subcommands/default |
| 3. Check for subcommands/<cmd> |
| 4. Fall back to commands scripts |
+-------------------+-------------------+
|
v
+-------------------+-------------------+
| plugins/apps/subcommands/list |
| (executes the actual command) |
+-------------------+-------------------+
Command resolution order:
- Check
$PLUGIN_ENABLED_PATH/<plugin>/subcommands/defaultfor the default command - Check
$PLUGIN_ENABLED_PATH/<plugin>/subcommands/<command>for named commands - Iterate through all
$PLUGIN_ENABLED_PATH/*/commandsscripts as fallback
Deployment Pipeline¶
When code is pushed via git push dokku@host:myapp, Dokku executes a multi-stage pipeline:
+-------------+ +-------------+ +-------------+ +-------------+
| Receive |--->| Build |--->| Release |--->| Deploy |
+-------------+ +-------------+ +-------------+ +-------------+
| | | |
v v v v
git-hook builder-detect builder-release scheduler-deploy
receive-app pre-build core-post-deploy
post-extract builder-build post-deploy
Stage 1: Receive¶
The git receive stage handles the incoming push and prepares the source code:
git-hookreceives the push and validates the branchreceive-apptrigger is fired- Source code is extracted to a temporary directory
post-extracttrigger allows modification of source
Stage 2: Build¶
The build stage creates a Docker image from the source:
builder-detectdetermines the builder type (herokuish, pack, dockerfile, etc.)pre-buildtrigger runs pre-build hooksbuilder-buildcreates the Docker imagepost-buildtrigger runs post-build hooks
Stage 3: Release¶
The release stage prepares the image for deployment:
pre-release-builderallows image modificationsbuilder-releasesets environment variables in the imagepost-release-builderruns final release hooks
Stage 4: Deploy¶
The deploy stage starts containers and configures networking:
scheduler-deploystarts new containerscheck-deployruns health checkscore-post-deployswitches traffic to new containerspost-deployruns deployment tasks- Old containers are retired
State Management¶
Dokku uses a file-based state system for simplicity and transparency.
Property System¶
Plugin-specific configuration is stored in the property system:
Properties are managed via helper functions:
# Shell
fn-plugin-property-write "git" "$APP" "deploy-branch" "main"
fn-plugin-property-get "git" "$APP" "deploy-branch"
// Go
common.PropertyWrite("git", appName, "deploy-branch", "main")
common.PropertyGet("git", appName, "deploy-branch")
Per-App Data¶
Application-specific data sometimes lives in $DOKKU_ROOT/<app>/:
refs/- Git referencestls/- SSL certificatesENV- Environment file- Container IDs, port mappings, etc.
All non-git code is being migrated to the property system.
Global Configuration¶
Global settings can be configured via:
/etc/default/dokku- System-level defaults$DOKKU_ROOT/dokkurc- User-level configuration$DOKKU_ROOT/.dokkurc/*- Additional configuration files
Key Design Decisions¶
| Decision | Rationale |
|---|---|
| Plugin-based architecture | Enables extensibility without modifying core code. Community plugins can add databases, caching, and other services. |
| Bash + Go hybrid | Bash for orchestration and simple commands; Go for performance-critical operations and complex logic. |
| Trigger system | Loose coupling between plugins. Plugins don't need to know about each other; they just fire and respond to events. |
| File-based state | Simple, transparent, and easy to debug. No database dependency. State can be inspected with standard Unix tools. |
| Docker as foundation | Leverages Docker's container runtime, networking, and image management. Allows multiple scheduler backends. |
Further Reading¶
- Plugin Creation - How to create custom plugins
- Plugin Triggers - Complete list of available triggers
- Testing - How to test Dokku and plugins
- Application Deployment - User-facing deployment guide
- Builder Management - Available build backends
- Scheduler Management - Available scheduler backends