A NixOS module that runs Dokploy using declarative systemd units.
NixOS-only — uses systemd.services and systemd.tmpfiles directly.
dokploy-stack.serviceanddokploy-traefik.servicesystemd units- Service ordering:
docker.service→dokploy-stack.service→dokploy-traefik.service - State directory creation via
systemd.tmpfiles - Clean stop/restart (containers removed on stop)
- No reliance on upstream shell scripts
- Docker enabled with
live-restore = false(required for swarm) - Rootless Docker is not supported (swarm limitation)
# flake.nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
nix-dokploy.url = "github:el-kurto/nix-dokploy";
nix-dokploy.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { self, nixpkgs, nix-dokploy, ... }: {
nixosConfigurations.my-server = nixpkgs.lib.nixosSystem {
modules = [
nix-dokploy.nixosModules.default
{
virtualisation.docker.enable = true;
virtualisation.docker.daemon.settings.live-restore = false;
services.dokploy.enable = true;
services.dokploy.database.passwordFile = "/var/lib/secrets/dokploy-db-password";
}
];
};
};
}Generate a password file on the host before deploying:
mkdir -p /var/lib/secrets
openssl rand -base64 32 > /var/lib/secrets/dokploy-db-passwordDokploy will be available at http://your-server-ip:3000
| Option | Default | Description |
|---|---|---|
dataDir |
/var/lib/dokploy |
Data directory |
image |
dokploy/dokploy:v0.28.4 |
Dokploy Docker image |
environment |
{} |
Environment variables for the Dokploy container |
lxc |
false |
LXC compatibility mode (e.g. Proxmox) |
services.dokploy.environment = {
TZ = "Europe/Amsterdam";
};| Option | Default | Description |
|---|---|---|
port |
"3000:3000" |
Port binding for web UI |
hostPortMode |
false |
Use "host" port mode instead of "ingress" |
Docker bypasses host firewall rules, so "3000:3000" exposes the port to the internet regardless of iptables/nftables.
Once Traefik is set up as a reverse proxy, disable direct access:
services.dokploy.port = null;| Option | Default | Description |
|---|---|---|
database.passwordFile |
— (required) | Path to file containing the PostgreSQL password |
database.useInsecureHardcodedPassword |
false |
Use the old hardcoded password (migration aid only) |
The password is stored as a Docker secret. Generate one before deploying:
openssl rand -base64 32 > /var/lib/secrets/dokploy-db-passwordservices.dokploy.database.passwordFile = "/var/lib/secrets/dokploy-db-password";Previous versions used a hardcoded password. On upgrade, nixos-rebuild will fail asking you to set database.passwordFile or enable database.useInsecureHardcodedPassword.
Option A: Keep the old password temporarily
services.dokploy.database.useInsecureHardcodedPassword = true;A build warning will remind you to migrate.
Option B: Migrate to a secure password
Complete these steps in order. The old stack must still be running for step 2.
-
Generate a new password file:
openssl rand -base64 32 > /var/lib/secrets/dokploy-db-password -
Change the password in the running PostgreSQL container. As root, open a psql shell:
docker exec -it $(docker ps --filter "name=dokploy_postgres" -q) psql -U dokploy -d dokploy
Then set the password to match the contents of your password file:
ALTER USER dokploy WITH PASSWORD 'contents-of-password-file';
Do not pass the password via command-line flags or environment variables — it will be visible in the process list.
-
Deploy with
database.passwordFileset.
Docker secrets are immutable, so the deploy script won't update an existing secret. To rotate, run these steps as root:
- Generate a new password file:
openssl rand -base64 32 > /var/lib/secrets/dokploy-db-password - Change the password in the running PostgreSQL container:
docker exec -it $(docker ps --filter "name=dokploy_postgres" -q) psql -U dokploy -d dokploy
ALTER USER dokploy WITH PASSWORD 'contents-of-password-file';
- Remove the stack:
docker stack rm dokploy - Remove the old secret:
docker secret rm dokploy_postgres_password - Redeploy with
nixos-rebuild switch
| Option | Default | Description |
|---|---|---|
swarm.advertiseAddress |
"private" |
IP address Docker Swarm advertises |
swarm.autoRecreate |
false |
Recreate swarm on IP change during restart |
services.dokploy.swarm.advertiseAddress = "private"; # first private IP (default)
services.dokploy.swarm.advertiseAddress = "public"; # public IP via ifconfig.me
# custom command
services.dokploy.swarm.advertiseAddress = {
command = "tailscale ip -4 | head -n1";
extraPackages = [ pkgs.tailscale ];
};
# recreate swarm if IP changes (safe for single-node only)
services.dokploy.swarm.autoRecreate = true;Using "public" exposes swarm management ports (2377, 7946, 4789) to the internet. Consider Tailscale/WireGuard or private networking instead.
| Option | Default | Description |
|---|---|---|
traefik.image |
traefik:v3.6.13 |
Traefik Docker image |
traefik.extraArgs |
[] |
Extra docker run flags |
traefik.certificates |
{} |
TLS certificate pairs |
traefik.dynamicConfig |
{} |
Dynamic config as Nix attrsets (generates YAML) |
traefik.files |
{} |
Files to place in the dynamic config directory |
services.dokploy.traefik.extraArgs = [
"-e CF_API_EMAIL=user@example.com"
"-e CF_API_KEY=your_api_key"
"-v /path/to/certs:/certs"
];Creates a subdirectory under traefik/dynamic/certificates/<name>/ with chain.crt, privkey.key, and a certificate.yml.
services.dokploy.traefik.certificates."cloudflare-origin" = {
certFile = "/var/lib/secrets/cloudflare-origin-ca.pem";
keyFile = "/var/lib/secrets/cloudflare-origin-ca-key.pem";
};Each key becomes a .yml file in the Traefik dynamic config directory.
services.dokploy.traefik.dynamicConfig."cloudflare-client-auth" = {
tls.options.default.clientAuth = {
caFiles = [ "/etc/dokploy/traefik/dynamic/files/cloudflare-origin-pull-ca.pem" ];
clientAuthType = "RequireAndVerifyClientCert";
};
};Files are placed at traefik/dynamic/files/<name> on the host and visible in the container at /etc/dokploy/traefik/dynamic/files/<name>.
services.dokploy.traefik.files."cloudflare-origin-pull-ca.pem" = pkgs.fetchurl {
url = "https://developers.cloudflare.com/ssl/static/authenticated_origin_pull_ca.pem";
sha256 = "...";
};Dokploy itself is Apache 2.0 with additional terms.


