skip to content
wihan.dev
view as markdown
browse the dokploy docs

Usage examples

Each example is a complete configuration on top of the provider block from Get started. Copy one, replace the values, and apply. The examples use production_environment_id, the environment that Dokploy creates with each project.

An application from GitLab, with Slack alerts

The GitLab connection holds the OAuth application that you created in GitLab. After the first apply, open Git > GitLab in Dokploy and authorize it once. The application then deploys from the project on each push to main, and Slack gets a message for each deploy and each failed build.

hcl
resource "dokploy_project" "shop" {
  name = "shop"
}

resource "dokploy_gitlab_provider" "main" {
  name              = "my-group"
  application_id    = var.gitlab_oauth_application_id
  secret_wo         = var.gitlab_oauth_secret
  secret_wo_version = 1
  group_name        = "my-group"
}

resource "dokploy_application" "api" {
  name           = "api"
  environment_id = dokploy_project.shop.production_environment_id

  gitlab = {
    gitlab_id      = dokploy_gitlab_provider.main.id
    owner          = "my-group"
    repository     = "api"
    branch         = "main"
    project_id     = 12345678
    path_namespace = "my-group/api"
  }

  build = {
    type = "nixpacks"
  }

  env = <<-EOT
    PORT=3000
  EOT
}

resource "dokploy_domain" "api" {
  application_id = dokploy_application.api.id
  host           = "api.example.com"
  port           = 3000
  https          = true
}

resource "dokploy_slack_notification" "deploys" {
  name                   = "deploys"
  channel                = "#deploys"
  webhook_url_wo         = var.slack_webhook_url
  webhook_url_wo_version = 1

  app_deploy      = true
  app_build_error = true
}

A remote worker server with a database on it

The SSH key pair comes from the hashicorp/tls provider. Dokploy stores the server record; after the first apply, open Settings > Servers in Dokploy and run Setup Server once, which installs Docker on the machine. The database then runs on the worker through server_id.

hcl
resource "tls_private_key" "worker" {
  algorithm = "ED25519"
}

resource "dokploy_ssh_key" "worker" {
  name                   = "worker"
  public_key             = tls_private_key.worker.public_key_openssh
  private_key_wo         = tls_private_key.worker.private_key_openssh
  private_key_wo_version = 1
}

resource "dokploy_server" "worker" {
  name       = "worker-1"
  ip_address = "203.0.113.10"
  ssh_key_id = dokploy_ssh_key.worker.id
}

resource "dokploy_project" "data" {
  name = "data"
}

resource "dokploy_postgres" "db" {
  name                         = "db"
  environment_id               = dokploy_project.data.production_environment_id
  server_id                    = dokploy_server.worker.id
  database_name                = "app"
  database_user                = "app"
  database_password_wo         = var.db_password
  database_password_wo_version = 1
}

Add the public key to ~root/.ssh/authorized_keys on the machine before the setup: Dokploy signs in as root with the private key.

Private images from a registry

The registry login lets Dokploy pull a private image. Dokploy runs docker login on the server when it stores the record, so the token must be valid at apply time.

hcl
resource "dokploy_registry" "ghcr" {
  name                = "ghcr"
  url                 = "ghcr.io"
  username            = "my-org-bot"
  password_wo         = var.ghcr_token
  password_wo_version = 1
}

resource "dokploy_project" "internal" {
  name = "internal"
}

resource "dokploy_application" "worker" {
  name           = "worker"
  environment_id = dokploy_project.internal.production_environment_id

  docker = {
    image        = "ghcr.io/my-org/worker:2.1.0"
    registry_url = "ghcr.io"
    username     = "my-org-bot"
    password     = var.ghcr_token
  }
}

An application that Dokploy builds can push its image to the same registry: set registry_id = dokploy_registry.ghcr.id on it.

A teammate with limited access

The user gets an initial password and the member role. The permissions resource then opens one project to them and lets them create services, but not delete them.

hcl
resource "dokploy_project" "shop" {
  name = "shop"
}

resource "dokploy_user" "dev" {
  email               = "[email protected]"
  role                = "member"
  password_wo         = var.dev_initial_password
  password_wo_version = 1
}

resource "dokploy_user_permissions" "dev" {
  user_id             = dokploy_user.dev.id
  accessed_projects   = [dokploy_project.shop.id]
  can_create_services = true
  can_delete_services = false
}

For a person who was invited in the Dokploy UI, look the user up by email with the dokploy_user data source instead of the dokploy_user resource.

Nightly backups with an email alert

The destination is an S3-compatible bucket. The backup dumps the database every night at 03:00, and the email channel reports each backup result.

hcl
resource "dokploy_destination" "backups" {
  name                         = "backups"
  provider_name                = "Cloudflare"
  endpoint                     = "https://${var.r2_account_id}.r2.cloudflarestorage.com"
  bucket                       = "dokploy-backups"
  region                       = "auto"
  access_key_wo                = var.r2_access_key
  access_key_wo_version        = 1
  secret_access_key_wo         = var.r2_secret_access_key
  secret_access_key_wo_version = 1
}

resource "dokploy_backup" "db" {
  service_id      = dokploy_postgres.db.id
  service_type    = "postgres"
  destination_id  = dokploy_destination.backups.id
  database        = "app"
  prefix          = "shop/"
  cron_expression = "0 3 * * *"
}

resource "dokploy_email_notification" "backups" {
  name                = "backup-mail"
  smtp_server         = "smtp.example.com"
  smtp_port           = 587
  username            = "[email protected]"
  password_wo         = var.smtp_password
  password_wo_version = 1
  from_address        = "[email protected]"
  to_addresses        = ["[email protected]"]

  database_backup = true
}

Environment variables as a map

dokploy_environment_variables owns the whole variable list of one application, compose, or environment. The target must not manage env itself, so it carries an ignore_changes block.

hcl
resource "dokploy_application" "api" {
  name           = "api"
  environment_id = dokploy_project.shop.production_environment_id

  docker = {
    image = "ghcr.io/my-org/api:1.4.2"
  }

  lifecycle {
    ignore_changes = [env]
  }
}

resource "dokploy_environment_variables" "api" {
  application_id = dokploy_application.api.id

  variables = {
    PORT      = "3000"
    LOG_LEVEL = "info"
    DB_URL    = "postgres://app:${var.db_password}@${dokploy_postgres.db.app_name}:5432/app"
  }
}

A change to the map does not redeploy the application. Dokploy applies the variables on the next deploy.

A highly available application with Swarm settings

The swarm attribute models the Docker Swarm service specification. This example runs two replicas, starts the new task before it stops the old task on an update, and checks the health of each container. Durations are in nanoseconds: 10000000000 is 10 seconds.

hcl
resource "dokploy_project" "ha" {
  name = "ha"
}

resource "dokploy_application" "api" {
  name           = "api"
  environment_id = dokploy_project.ha.production_environment_id

  docker = {
    image = "traefik/whoami:v1.10"
  }

  swarm = {
    mode = {
      replicated = { replicas = 2 }
    }

    update_config = {
      parallelism = 1
      order       = "start-first"
    }

    health_check = {
      test         = ["CMD", "wget", "-q", "-O", "/dev/null", "http://localhost:80"]
      interval     = 10000000000
      timeout      = 5000000000
      retries      = 3
      start_period = 10000000000
    }
  }
}

Set the replica count in swarm.mode.replicated.replicas. The top-level replicas attribute and swarm.mode cannot both be set. The provider rejects the pair at plan time, because Dokploy uses the mode and ignores replicas. Use replicas for a simple count, or swarm.mode for all other modes. Swarm cannot change the mode of a service that exists.

A change to swarm reaches the service at the next deploy. See Deploy semantics. The six database resources take the same block. dokploy_compose has no swarm block, and dokploy_libsql does not accept swarm.ulimits.

A full stack: database, application, domain, and backup

This stack makes one random password and gives it to the database and to the application. The application reads the database URL through env.

hcl
terraform {
  required_providers {
    dokploy = {
      source  = "vanillauys/dokploy"
      version = "~> 1.8"
    }
    random = {
      source  = "hashicorp/random"
      version = "~> 3.7"
    }
  }
}

resource "random_password" "db" {
  length  = 32
  special = false
}

resource "dokploy_project" "shop" {
  name = "shop"
}

resource "dokploy_postgres" "db" {
  name              = "shop-db"
  environment_id    = dokploy_project.shop.production_environment_id
  database_name     = "shop"
  database_user     = "shop"
  database_password = random_password.db.result
  docker_image      = "postgres:16-alpine"
}

resource "dokploy_application" "web" {
  name           = "web"
  environment_id = dokploy_project.shop.production_environment_id

  docker = {
    image = "ghcr.io/my-org/shop:1.0.0"
  }

  env = <<-EOT
    PORT=3000
    DATABASE_URL=postgres://shop:${random_password.db.result}@${dokploy_postgres.db.app_name}:5432/shop
  EOT
}

resource "dokploy_domain" "web" {
  application_id   = dokploy_application.web.id
  host             = "shop.example.com"
  port             = 3000
  https            = true
  certificate_type = "letsencrypt"
}

resource "dokploy_destination" "backups" {
  name                         = "backups"
  provider_name                = "AWS"
  endpoint                     = "https://s3.eu-west-1.amazonaws.com"
  bucket                       = "shop-backups"
  region                       = "eu-west-1"
  access_key_wo                = var.s3_access_key
  access_key_wo_version        = 1
  secret_access_key_wo         = var.s3_secret_access_key
  secret_access_key_wo_version = 1
}

resource "dokploy_backup" "db" {
  service_id      = dokploy_postgres.db.id
  service_type    = "postgres"
  destination_id  = dokploy_destination.backups.id
  database        = "shop"
  prefix          = "shop/"
  cron_expression = "0 3 * * *"
}

The env attribute is not write-only, so the password is in the state. The random_password resource keeps the value in the state, and Dokploy returns it on read. Use a state backend with encryption at rest, and limit who can read it. Secrets and sensitive values explains the trade-off.

For a database that no application reads through env, keep the password out of the state with the write-only attributes.

A backup of the Dokploy host

dokploy_web_server_backup dumps the database and the configuration directory of Dokploy itself to a destination. It is a separate resource from dokploy_backup, because it has no parent service. The provider does not run a backup on demand.

hcl
resource "dokploy_destination" "host" {
  name                         = "host-backups"
  provider_name                = "AWS"
  endpoint                     = "https://s3.eu-west-1.amazonaws.com"
  bucket                       = "dokploy-host-backups"
  region                       = "eu-west-1"
  access_key_wo                = var.s3_access_key
  access_key_wo_version        = 1
  secret_access_key_wo         = var.s3_secret_access_key
  secret_access_key_wo_version = 1
}

resource "dokploy_web_server_backup" "nightly" {
  destination_id  = dokploy_destination.host.id
  cron_expression = "0 2 * * *"
  prefix          = "dokploy/"

  keep_latest_count = 14
}

include_encryption_key is true by default, so the backup holds the key that decrypts the stored secrets. Store the destination bucket with the same care as the state.

Check a connection before the write

verify_connection = true tests the credentials before the provider writes the record. A failed test fails the apply with the message of the server, and the provider writes nothing.

hcl
resource "dokploy_registry" "ghcr" {
  name                = "ghcr"
  url                 = "ghcr.io"
  username            = "my-org-bot"
  password_wo         = var.ghcr_token
  password_wo_version = 1

  verify_connection = true
}

resource "dokploy_destination" "backups" {
  name                         = "backups"
  provider_name                = "AWS"
  endpoint                     = "https://s3.eu-west-1.amazonaws.com"
  bucket                       = "my-backups"
  region                       = "eu-west-1"
  access_key_wo                = var.s3_access_key
  access_key_wo_version        = 1
  secret_access_key_wo         = var.s3_secret_access_key
  secret_access_key_wo_version = 1

  verify_connection = true
}

The Secrets guide lists the resources that take the attribute and the cases in which the test is not reliable.

Look up existing records

The data sources give a second workspace a reference to a record without an import. A service child needs the id of its parent and one distinguishing attribute. A notification or an AI record needs an id or a name. No data source returns a secret.

hcl
variable "application_id" {
  type = string
}

data "dokploy_port" "http" {
  application_id = var.application_id
  published_port = 8080
}

data "dokploy_slack_notification" "deploys" {
  name = "deploys"
}

data "dokploy_ai" "main" {
  name = "openai"
}

output "port_id" {
  value = data.dokploy_port.http.id
}

output "deploys_notification_id" {
  value = data.dokploy_slack_notification.deploys.id
}

output "ai_model" {
  value = data.dokploy_ai.main.model
}

A lookup that matches no record, or more than one record, fails the plan.

Where to go next

  • The Secrets guide explains the _wo companions that each example uses, and what stays in the state.
  • The Deploy semantics guide explains when a change deploys and how long the provider waits.
  • The Adopt guide explains how to bring a server that the UI configured under Terraform.