Skip to content

Terraform Provider Overview

The official DanubeData Terraform provider allows you to manage your cloud infrastructure using Infrastructure as Code (IaC). Deploy VPS instances, databases, caches, storage buckets, and more with version-controlled, reproducible configurations.

Why Use Terraform?

  • Version Control: Track infrastructure changes in Git alongside your application code
  • Reproducible Deployments: Deploy identical environments across dev, staging, and production
  • Team Collaboration: Share configurations and collaborate with pull requests
  • Drift Detection: Detect and fix configuration drift automatically
  • Cost Visibility: Preview costs before deployment with terraform plan

Quick Start

1. Install Terraform

Download and install Terraform from terraform.io:

Bash
# macOS with Homebrew
brew tap hashicorp/tap
brew install hashicorp/tap/terraform

# Ubuntu/Debian
wget -O- https://apt.releases.hashicorp.com/gpg | sudo gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install terraform

# Verify installation
terraform version

2. Get Your API Token

  1. Log in to your DanubeData dashboard
  2. Navigate to Security > API tokens
  3. Click Create token
  4. Select the permissions you need (at minimum: read/write for resources you want to manage)
  5. Copy the token - it's shown only once!

3. Configure the Provider

Create a new directory for your Terraform configuration:

Bash
mkdir my-infrastructure
cd my-infrastructure

Create a main.tf file:

HCL
terraform {
  required_providers {
    danubedata = {
      source  = "AdrianSilaghi/danubedata"
      version = "~> 0.4"
    }
  }
}

provider "danubedata" {
  # API token is read from DANUBEDATA_API_TOKEN environment variable
}

Set your API token as an environment variable:

Bash
export DANUBEDATA_API_TOKEN="your-api-token-here"

4. Initialize and Apply

Bash
# Initialize the provider
terraform init

# Preview changes
terraform plan

# Apply changes
terraform apply

Provider Configuration

The provider supports the following configuration options:

AttributeTypeRequiredDescription
api_tokenstringYesYour DanubeData API token. Can also be set via DANUBEDATA_API_TOKEN environment variable.
base_urlstringNoAPI endpoint URL. Defaults to https://danubedata.ro/api/v1. Can be set via DANUBEDATA_BASE_URL environment variable.
team_idnumberNoThe ID of the project to work in. Needs provider 0.4.0 or later. Can also be set via DANUBEDATA_TEAM_ID environment variable. See Selecting a project.

Authentication Methods

Recommended: Environment Variable

Bash
export DANUBEDATA_API_TOKEN="your-api-token"

HCL
provider "danubedata" {}

Alternative: Direct Configuration (not recommended for production)

HCL
provider "danubedata" {
  api_token = "your-api-token"  # Don't commit this to version control!
}

Using Terraform Variables

HCL
variable "danubedata_token" {
  type      = string
  sensitive = true
}

provider "danubedata" {
  api_token = var.danubedata_token
}

Then set the variable:

Bash
export TF_VAR_danubedata_token="your-api-token"

Selecting a project

Every resource belongs to one project, and one provider block works in one project. Set team_id to choose the project, or set the DANUBEDATA_TEAM_ID environment variable. Both take the project's ID, a whole number, and need provider 0.4.0 or later. The API calls a project a team, which is why the setting is named team_id.

HCL
provider "danubedata" {
  team_id = 123
}

The provider sends the ID with every request. If you leave it out, the API picks the project from your token:

  • A token made with This project only works in that project. If you set team_id to a different project, the API refuses the request with 403 Forbidden.
  • A token made with All your projects works in your current project, the one you last switched to in the console.

Set team_id if your token can reach more than one project. Your current project changes each time you switch project in the console, so the same configuration can end up in a different project. From 19 November 2026 a request works in one project only, and a resource in any other project answers 404 Not Found. Terraform reads that as "deleted outside Terraform": it removes the resource from its state and plans to create it again. Applying that plan makes a second copy, which is billed, while the original keeps running in its own project.

Working with more than one project

One provider block cannot switch projects. Add one block for each project, give each an alias, and name the block you want with provider on every resource and data source:

HCL
provider "danubedata" {
  alias   = "production"
  team_id = 123
}

provider "danubedata" {
  alias   = "staging"
  team_id = 456
}

resource "danubedata_database" "production" {
  provider         = danubedata.production
  name             = "app-database"
  engine           = "postgresql"
  version          = "16"
  resource_profile = "small"
  datacenter       = "fsn1"
}

resource "danubedata_database" "staging" {
  provider         = danubedata.staging
  name             = "app-database"
  engine           = "postgresql"
  version          = "16"
  resource_profile = "small"
  datacenter       = "fsn1"
}

Each block needs a token that can reach its project. One token made with All your projects covers every block. Or set api_token in each block to a token made with This project only for that project.

Finding a project's ID

Run danube project ls with the Danube CLI. It prints each project's name and ID. You can also ask the API with any API token:

Bash
curl -H "Authorization: Bearer $DANUBEDATA_API_TOKEN" https://danubedata.ro/api/v1/user/teams

The answer lists your projects with their id and name. current_team_id is the project the token works in when no ID is sent, so it shows where Terraform would act without team_id.

Available Resources

The provider includes the following resource types:

ResourceDescription
danubedata_vpsVirtual machines with shared or dedicated CPU
danubedata_databaseManaged MySQL, PostgreSQL, and MariaDB databases
danubedata_database_replicaRead replicas for a managed database
danubedata_cacheRedis, Valkey, and Dragonfly cache instances
danubedata_parameter_groupReusable database/cache configuration presets
danubedata_serverlessScale-to-zero serverless containers
danubedata_static_siteStatic site hosting
danubedata_static_site_domainCustom domain for a static site
danubedata_storage_bucketS3-compatible object storage buckets
danubedata_storage_access_keyAccess keys for object storage
danubedata_ssh_keySSH keys for VPS authentication
danubedata_firewallNetwork security rules
danubedata_vps_snapshotPoint-in-time VPS backups
danubedata_database_snapshotPoint-in-time database backups
danubedata_cache_snapshotPoint-in-time cache backups

See Terraform Resources for detailed documentation.

Available Data Sources

The provider includes these data sources for querying existing resources:

Data SourceDescription
danubedata_vpssList existing VPS instances
danubedata_vps_imagesList available OS images
danubedata_databasesList existing databases
danubedata_database_providersList available database engines
danubedata_database_snapshotsList database snapshots
danubedata_cachesList existing cache instances
danubedata_cache_providersList available cache providers
danubedata_cache_snapshotsList cache snapshots
danubedata_parameter_groupsList parameter groups
danubedata_serverless_containersList serverless containers
danubedata_static_sitesList static sites
danubedata_storage_bucketsList storage buckets
danubedata_storage_access_keysList storage access keys
danubedata_ssh_keysList existing SSH keys
danubedata_firewallsList firewalls
danubedata_vps_snapshotsList VPS snapshots

See Terraform Data Sources for detailed documentation.

Supported Datacenters

All resources are deployed to our single datacenter:

CodeLocationCountry
fsn1FalkensteinGermany

State Management

Terraform tracks your infrastructure state in a state file. The state — not your API token — records which resources Terraform already manages, so it is what decides whether a plan creates, changes or destroys something. For team collaboration, use a remote backend.

Using DanubeData Object Storage as Backend

HCL
terraform {
  backend "s3" {
    bucket = "terraform-state"
    key    = "my-project/terraform.tfstate"
    region = "fsn1"

    endpoints = { s3 = "https://s3.danubedata.ro" }

    use_path_style              = true
    use_lockfile                = true
    skip_credentials_validation = true
    skip_requesting_account_id  = true
    skip_metadata_api_check     = true
    skip_region_validation      = true
    skip_s3_checksum            = true
  }
}

The skip_* flags are required: our object storage is S3-compatible but is not AWS, so the AWS-specific credential, account-ID and metadata lookups have to be turned off. use_lockfile = true enables state locking against the bucket itself, with no DynamoDB table, so two CI runs cannot write the state at once — it needs Terraform 1.10 or later.

Set S3 credentials from a storage access key:

Bash
export AWS_ACCESS_KEY_ID="your-access-key"
export AWS_SECRET_ACCESS_KEY="your-secret-key"

Importing Existing Resources

You can import existing DanubeData resources into Terraform management:

Bash
# Import a VPS instance
terraform import danubedata_vps.my_server 12345

# Import a database
terraform import danubedata_database.my_db 67890

# Import a storage bucket
terraform import danubedata_storage_bucket.my_bucket abc123

Find resource IDs in the DanubeData dashboard or via the API.

Best Practices

Give Each Environment Its Own State

Separate environments need separate state. The API token and team_id decide which project a call goes to, but they do not change what Terraform already believes it manages — that comes from the state file.

Do not point one root module at several environments by changing a variable. If your state already holds a production resource and you re-run it with environment = "test", Terraform does not create something new: it compares the config against the resource it already tracks and plans to replace it. Attributes such as name, engine and database_name cannot be changed on a running instance, so the plan comes back as 1 to add, 1 to destroy — and the resource it destroys is the live production one.

Give each environment its own root module, its own state key and its own token:

Text
modules/stack/       shared resource definitions, taking env as a variable
envs/prod/           key prod/terraform.tfstate,  production token
envs/test/           key test/terraform.tfstate,  test token

Each environment directory holds only a backend block, a provider block and a call to the shared module:

HCL
# envs/test/main.tf
terraform {
  backend "s3" {
    bucket = "my-tfstate"
    key    = "test/terraform.tfstate"
    # ...remaining backend settings as above
  }
}

provider "danubedata" {}   # token from DANUBEDATA_API_TOKEN

module "stack" {
  source = "../../modules/stack"
  env    = "test"
}

Now terraform apply in envs/test/ starts from an empty state and creates the test resources, while production is untouched — because a different state file describes it.

Resource names are unique per project rather than globally, so the same name can be used in every environment and only values that must differ, such as a database's initial database name, need to include var.env.

Terraform workspaces are an alternative, but the API token still has to change per environment through the shell environment, so separate root modules are harder to get wrong.

Use One Project per Environment

Create a separate project for each environment. Point each environment's provider block at its project with team_id, and give it its own API token made with This project only. Projects are the isolation boundary on our side: separate Kubernetes namespace, separate quotas and separate billing lines.

HCL
# envs/prod/main.tf
provider "danubedata" {
  team_id = 123   # the production project
}

A token locked to its project cannot reach any other. If team_id names the wrong project, the API refuses the request instead of acting there. See Selecting a project.

Mark Sensitive Outputs

HCL
output "database_password" {
  value     = danubedata_database.main.password
  sensitive = true
}

Use Terraform Cloud for Team Collaboration

For larger teams, consider using Terraform Cloud for:

  • Remote state management
  • Policy enforcement
  • VCS integration
  • Cost estimation

Troubleshooting

Authentication Errors

If you see "401 Unauthorized":

  1. Verify your API token is correct
  2. Check the token has the required permissions
  3. Ensure the DANUBEDATA_API_TOKEN environment variable is set

Provider Not Found

If Terraform can't find the provider:

Bash
terraform init -upgrade

Resource Timeouts

For long-running operations, configure timeouts:

HCL
resource "danubedata_vps" "app" {
  # ...

  timeouts {
    create = "10m"
    update = "10m"
    delete = "5m"
  }
}

Next Steps

Support