# Features introduced in Dokploy v0.30.0: Docker networks, vault-backed # environment variables, scheduled jobs, and a volume that actually persists. terraform { required_providers { dokploy = { source = "maxvojtkov/dokploy" version = "~> 0.2.0" } } } provider "dokploy" { # host and api_key come from DOKPLOY_HOST and DOKPLOY_API_KEY } variable "vault_token" { type = string sensitive = true } resource "dokploy_project" "shop" { name = "shop" description = "Storefront and its backing services" } # --------------------------------------------------------------- Networking # A private overlay network. Only the services attached to it can reach each # other over it — the database never becomes reachable from unrelated # services that merely share the default dokploy-network. resource "dokploy_network" "backend" { name = "shop-backend" driver = "overlay" attachable = true internal = false } # ------------------------------------------------------------------ Secrets # Environment values are resolved from Vault when the deployment runs, so # rotating a secret takes effect on the next deploy with no Terraform change # and no secret in Terraform state. resource "dokploy_vault_provider" "prod" { name = "production-vault" config = jsonencode({ providerType = "hashicorp" url = "https://vault.example.com" token = var.vault_token mount = "secret" }) assignments = jsonencode([]) } # ---------------------------------------------------------------- Services resource "dokploy_postgres" "db" { name = "shop-db" environment_id = dokploy_project.shop.default_environment_id docker_image = "postgres:16-alpine" database_name = "shop" database_user = "shop" database_password = "set-me-from-a-variable" network_ids = [dokploy_network.backend.id] } resource "dokploy_application" "api" { name = "api" environment_id = dokploy_project.shop.default_environment_id source_type = "docker" docker_image = "ghcr.io/acme/api:1.4.0" network_ids = [dokploy_network.backend.id] # Resolved from the vault provider above at deploy time. env = <<-EOT DATABASE_URL=postgresql://shop:$${{vault.production.db_password}}@${dokploy_postgres.db.app_name}:5432/shop STRIPE_KEY=$${{vault.production.stripe_key}} EOT } # -------------------------------------------------------- Persistent volume # volume_name is what makes this persist. Without it Dokploy hands Docker an # empty source and every deploy gets a fresh anonymous volume — the provider # rejects that at plan time. resource "dokploy_mount" "uploads" { type = "volume" volume_name = "shop-uploads" mount_path = "/app/uploads" service_type = "application" service_id = dokploy_application.api.id } resource "dokploy_destination" "backups" { name = "s3-backups" provider_name = "s3" access_key = "set-me" secret_access_key = "set-me" bucket = "shop-backups" region = "eu-central-1" endpoint = "https://s3.eu-central-1.amazonaws.com" } # The named volume is only durable if it also leaves the host. resource "dokploy_volume_backup" "uploads" { name = "uploads-nightly" volume_name = dokploy_mount.uploads.volume_name prefix = "shop/uploads/" cron_expression = "0 4 * * *" destination_id = dokploy_destination.backups.id service_type = "application" application_id = dokploy_application.api.id keep_latest_count = 14 } # ---------------------------------------------------------------- Schedules resource "dokploy_schedule" "prune_sessions" { name = "prune-sessions" description = "Drop expired sessions every night" schedule_type = "application" application_id = dokploy_application.api.id cron_expression = "0 2 * * *" command = "node scripts/prune-sessions.js" timezone = "Europe/Berlin" } # --------------------------------------------------------------------- DNS resource "dokploy_dns_provider" "cloudflare" { name = "cloudflare" config = jsonencode({ providerType = "cloudflare" apiToken = "set-me-from-a-variable" }) } resource "dokploy_domain" "api" { application_id = dokploy_application.api.id host = "api.example.com" port = 3000 https = true certificate_type = "letsencrypt" # A domain can be parked without losing its configuration. enabled = true }