Dokku: Build Your Own PaaS on Any Linux Server
Imagine having the power of Heroku on your own hardware—no vendor lock‑in, full control over resources, and the flexibility to host any language or framework you love. That’s exactly what Dokku gives you: a lightweight, Docker‑based platform‑as‑a‑service (PaaS) you can spin up on any Linux server in minutes. In this guide we’ll walk through the fundamentals, set up a production‑ready Dokku instance, deploy a sample app, and explore real‑world scenarios where a self‑hosted PaaS shines.
What Is Dokku and Why It Matters
Dokku is often described as a “mini‑Heroku”. Under the hood it uses Docker containers to isolate each app, a set of buildpacks to compile source code, and a simple git push workflow that developers already recognize. Because it’s built on open‑source components, you can extend it with plugins, customize networking, and even integrate it with your existing CI/CD pipelines.
Running Dokku on a VM or a bare‑metal server gives you the same developer experience as a hosted PaaS, but you keep the data, the cost model, and the security policies under your own control. This makes Dokku an attractive choice for startups, agencies, and hobbyists who need a scalable, cost‑effective way to host multiple micro‑services.
Prerequisites Before You Start
- A fresh Linux server (Ubuntu 22.04 LTS is the most common choice)
- Root or sudo access
- At least 1 GB of RAM (2 GB+ is recommended for multiple apps)
- Domain name(s) pointing to the server’s IP (optional but highly recommended)
- Basic familiarity with
gitand Docker concepts
If you’re using a cloud provider, spin up a droplet or VM with the above specs, then SSH into it. All subsequent commands assume you’re operating as a user with sudo privileges.
Installing Dokku: Step‑by‑Step
1. Add the Dokku APT repository
sudo apt-get update
sudo apt-get install -y software-properties-common
sudo add-apt-repository "deb https://packagecloud.io/dokku/dokku/ubuntu/ $(lsb_release -cs) main"
wget -qO- https://packagecloud.io/dokku/dokku/gpgkey | sudo apt-key add -
sudo apt-get update
2. Install the Dokku package
sudo apt-get install -y dokku
During installation you’ll be prompted for a hostname and whether you want to enable HTTP/HTTPS. Enter your domain (e.g., apps.example.com) and say “yes” to both HTTP and HTTPS. Dokku will automatically configure nginx as a reverse proxy and obtain a free Let’s Encrypt certificate.
3. Verify the installation
dokku version
You should see something like Dokku v0.27.0. Open a browser and navigate to http://your-domain.com; you’ll see the default “Welcome to Dokku!” page.
Pro tip: If you’re behind a firewall, ensure ports80,443, and22(SSH) are open. Dokku also uses port5000for internal Docker communication, but this is not exposed externally.
First Deployment: A Simple Node.js App
Let’s deploy a minimal Node.js “Hello World” app to prove everything works. This example demonstrates the classic git push dokku master workflow.
1. Create the app on the server
ssh root@your-server.com
dokku apps:create hello-node
This registers a new application called hello-node and creates a corresponding /home/dokku/hello-node directory for Docker containers.
2. Set up the local repository
mkdir hello-node
cd hello-node
git init
cat > package.json <<EOF
{
"name": "hello-node",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"engines": {
"node": ">=14"
}
}
EOF
cat > index.js <<EOF
const http = require('http');
const port = process.env.PORT || 5000;
http.createServer((req, res) => {
res.end('👋 Hello from Dokku on ' + process.env.HOSTNAME);
}).listen(port, () => console.log(`Listening on ${port}`));
EOF
git add .
git commit -m "Initial commit"
3. Add Dokku remote and push
git remote add dokku dokku@your-server.com:hello-node
git push dokku master
Dokku detects the Node.js buildpack, builds a Docker image, and starts a container listening on the port assigned by the PORT environment variable. Once the push completes, open https://hello-node.your-domain.com to see the greeting.
Pro tip: For faster builds, enable thedokku-buildpacksplugin and cache Docker layers. Rundokku plugin:install https://github.com/dokku/dokku-buildpacks.git buildpacksand thendokku buildpacks:set hello-node https://github.com/heroku/heroku-buildpack-nodejs.git.
Managing Environment Variables and Secrets
Production apps need database URLs, API keys, and other secrets. Dokku stores these as environment variables that are injected into the container at runtime. The dokku config command mirrors Heroku’s config:set syntax.
# Example: Setting a PostgreSQL URL
dokku config:set hello-node DATABASE_URL="postgres://user:pass@db.example.com:5432/appdb"
# Verify
dokku config:show hello-node
For added security, you can use the dokku plugin:install command to add the dokku-letsencrypt plugin, which automatically renews TLS certificates without exposing the private key.
Deploying a Python Flask Application with a Custom Domain
Now let’s switch gears to a Python stack. This example shows how to attach a custom subdomain, configure a PostgreSQL service via the official Dokku plugin, and use a Dockerfile for fine‑grained control.
1. Install the PostgreSQL plugin
dokku plugin:install https://github.com/dokku/dokku-postgres.git postgres
dokku postgres:create mydb
dokku postgres:link mydb flask-app
The last command creates a DATABASE_URL environment variable inside the flask-app container, pointing to the newly provisioned database.
2. Create the Flask app locally
mkdir flask-app
cd flask-app
python3 -m venv venv
source venv/bin/activate
pip install flask gunicorn psycopg2-binary
cat > app.py <<EOF
import os
from flask import Flask
app = Flask(__name__)
@app.route('/')
def home():
db_url = os.getenv('DATABASE_URL', 'not set')
return f"👋 Flask on Dokku! DB: {db_url}"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=int(os.getenv('PORT', 5000)))
EOF
cat > requirements.txt <<EOF
Flask
gunicorn
psycopg2-binary
EOF
cat > Dockerfile <<EOF
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["gunicorn", "-b", "0.0.0.0:$PORT", "app:app"]
EOF
git init
git add .
git commit -m "Initial Flask app"
3. Create the app on Dokku and bind the domain
dokku apps:create flask-app
dokku domains:add flask-app api.example.com
git remote add dokku dokku@your-server.com:flask-app
git push dokku master
After the push finishes, navigate to https://api.example.com. You should see the greeting with the database URL printed, confirming that the PostgreSQL plugin is correctly linked.
Pro tip: Usedokku config:set flask-app DEBUG=Falseto disable Flask’s debug mode in production. Pair this withdokku nginx:build-config flask-appto regenerate the Nginx configuration after any domain changes.
Scaling and Managing Multiple Containers
One of Dokku’s strengths is the ability to scale horizontally by increasing the number of container instances for a given app. This is useful for handling traffic spikes or achieving high availability.
# Scale the Node app to 3 containers
dokku ps:scale hello-node web=3
# Verify
dokku ps:report hello-node
Dokku uses Docker’s built‑in load balancing via Nginx; incoming requests are distributed across all running containers. You can also set resource limits per container using the dokku config:set syntax with Docker flags.
dokku config:set hello-node DOKKU_WEB_MEMORY_LIMIT=256M
dokku config:set hello-node DOKKU_WEB_CPU_LIMIT=0.5
These settings prevent a single container from monopolizing the host’s RAM or CPU, a crucial safeguard when you host many apps on the same machine.
Real‑World Use Cases for Dokku
- Start‑up MVP hosting: Spin up a cheap VPS, deploy a prototype, and iterate quickly without paying per‑dyno fees.
- Agency multi‑client platform: Give each client their own subdomain and isolated container, while sharing the same underlying hardware.
- Internal CI/CD sandbox: Developers push feature branches to
dokkuto spin up preview environments that disappear after testing. - Educational labs: Teach students about Docker, CI/CD, and PaaS concepts by letting them manage real deployments.
Because Dokku is just a thin layer over Docker, you can integrate it with existing orchestration tools like Kubernetes (as a “gateway” for simple apps) or with monitoring stacks such as Prometheus + Grafana for deep observability.
Backup Strategies and Disaster Recovery
Running a PaaS means you’ll eventually need to protect data. Dokku itself is stateless, but attached services (databases, file storage) are not. The recommended approach is to schedule regular dumps of each service and store them off‑site.
# Example: Daily PostgreSQL backup for app "flask-app"
dokku postgres:export mydb > /var/backups/mydb_$(date +%F).sql
# Upload to S3 (requires AWS CLI configured)
aws s3 cp /var/backups/mydb_$(date +%F).sql s3://my-backups/dokku/
Automate the above with a cron job and rotate old backups to keep storage costs low. For container‑level snapshots, Docker’s commit command can capture the exact filesystem state, but it’s generally better to rely on source control and reproducible Dockerfiles.
Pro tip: Use the dokku plugin:install https://github.com/dokku/dokku-snapshot.git snapshot plugin to create point‑in‑time snapshots of the entire Dokku environment with a single command.
Monitoring, Logging, and Performance Tweaks
Out of the box Dokku forwards container logs to docker logs, which you can view with dokku logs. For production, pipe these logs to a centralized system like Papertrail, Loki, or ELK.
# Forward logs to syslog (example for Papertrail)
dokku config:set hello-node LOGSPOUT=papertrail
dokku config:set hello-node PAPERTRAIL_HOST=logs.papertrailapp.com
dokku config:set hello-node PAPERTRAIL_PORT=12345
Metrics such as CPU, memory, and network I/O can be gathered with cAdvisor or the dokku-monitor plugin. Install it with:
dokku plugin:install https://github.com/dokku/dokku-monitor.git monitor
dokku monitor:enable
Once enabled, you’ll have a simple dashboard at /monitor on your server that visualizes per‑app resource consumption.
Extending Dokku with Plugins
Dokku’s plugin architecture is one of its most powerful features. Over 150 community plugins exist, covering everything from SSL automation to custom buildpacks. Here are three favorites for production workloads:
- dokku-letsencrypt – automatically fetches and renews free TLS certificates.
- dokku-redis – spins up a Redis instance and links it to your app with a single command.
- dokku-autodeploy – watches a GitHub repository and triggers a deployment on every push.
Installing a plugin is as simple as:
dokku plugin:install https://github.com/dokku/dokku-redis.git redis
dokku redis:create mycache
dokku redis:link mycache hello-node
After linking, the REDIS_URL environment variable becomes available inside your app, ready for use with any Redis client library.
Security Best Practices
- SSH keys only: Disable password authentication on the server and enforce key‑based logins for the
dokkuuser. - Least‑privilege containers: Use the
dokku security:enablecommand to drop unnecessary Linux capabilities. - Automatic updates: Set up unattended upgrades for the host OS and schedule
dokku plugin:updateweekly. - Network isolation: If you host multiple apps, consider creating a Docker network per app and using firewall rules to limit inter‑app traffic.
Combine these with regular vulnerability scans (e.g., trivy on Docker images) to keep your PaaS hardened against emerging threats.
Cost Comparison: Dokku vs. Managed PaaS
Let’s do a quick back‑of‑the‑envelope calculation. A small DigitalOcean droplet (2 vCPU, 4 GB RAM, 80 GB SSD) costs $12/month. With Dokku you can run 5–10 small apps on that single droplet, each consuming ~200 MB RAM. In contrast, the same number of apps on a managed service like Heroku would cost roughly $25–$40/month per dyno, plus add‑ons for databases and SSL.
The trade‑off is operational overhead: you must maintain the host, handle backups, and monitor health. However, for teams comfortable with Linux administration, the cost savings can be substantial, especially at scale.
Troubleshooting Common Issues
Problem