Skip to content

Terraform + UpCloud — Pitfalls

OWNER: arjan
ALSO_USED_BY: gerco, thijmen, rutger
LAST_VERIFIED: 2026-03-26
GE_STACK_VERSION: terraform ~> 1.14.x, upcloud provider ~> 5.0


Overview

Known Terraform and UpCloud provider failure modes.
Every item here has caused real issues in GE infrastructure.


State Drift

Severity: HIGH

State drift occurs when real infrastructure changes outside Terraform
(manual UpCloud console edits, API calls, other tools).

IF: terraform plan shows unexpected changes
THEN: run terraform refresh to sync state with reality
THEN: investigate who changed the resource outside Terraform

IF: drift is intentional (emergency fix)
THEN: import the change into state or update the config to match

ANTI_PATTERN: making changes in the UpCloud console for managed resources
FIX: all changes go through Terraform — console is read-only for managed resources

CHECK: run terraform plan weekly to detect drift early
RUN: terraform plan -detailed-exitcode (exit code 2 = drift detected)

ADDED_FROM: staging-firewall-drift-2026-02, manual console rule addition


Provider Version Mismatches

Severity: HIGH

Different team members running different provider versions causes state
corruption or incompatible plans.

IF: terraform init warns about provider version mismatch
THEN: check .terraform.lock.hcl is committed and up to date
RUN: terraform providers lock -platform=linux_amd64

CHECK: .terraform.lock.hcl is committed to git
CHECK: all team members run the same Terraform version
CHECK: CI/CD pins the exact Terraform version

ANTI_PATTERN: running terraform init -upgrade without coordinating
FIX: provider upgrades are a deliberate, reviewed action

ADDED_FROM: provider-5.0-upgrade-2026-01, state incompatibility after solo upgrade


Destroy Protection Bypass

Severity: CRITICAL

lifecycle.prevent_destroy = true protects resources from accidental deletion.
But removing the lifecycle block and running apply WILL destroy the resource.

IF: removing prevent_destroy from a resource
THEN: this MUST be a separate, reviewed commit
THEN: arjan must approve for Zone 2+3 resources
THEN: re-add prevent_destroy immediately after the operation

ANTI_PATTERN: removing prevent_destroy "temporarily" and forgetting to re-add
FIX: treat it as a two-commit operation — remove, act, re-add

ADDED_FROM: near-miss-db-destroy-2026-02, prevent_destroy removed in refactoring commit


Import Issues

Severity: MEDIUM

When importing existing UpCloud resources into Terraform state,
the config must match the actual resource exactly.

IF: importing a resource
THEN: write the config FIRST based on the UpCloud console/API
THEN: run terraform import
THEN: run terraform plan to verify no changes

RUN: terraform import upcloud_server.example {uuid}

IF: plan shows changes after import
THEN: adjust the config to match reality, not the other way around

ANTI_PATTERN: importing then immediately applying to "fix" differences
FIX: make config match reality first, then plan intentional changes separately

ADDED_FROM: lb-import-2026-03, import triggered unintended reconfiguration


Sensitive Values in State

Severity: HIGH

Terraform state contains the full resource config, including passwords
and API keys in plain text. Even with remote backend, state access = secret access.

CHECK: state backend has encryption at rest enabled
CHECK: state backend access is restricted to Terraform service accounts
CHECK: sensitive variables marked sensitive = true
CHECK: sensitive outputs marked sensitive = true

IF: state file was accidentally exposed
THEN: rotate ALL secrets referenced in that state immediately


UpCloud API Rate Limits

Severity: MEDIUM

UpCloud API has rate limits. Large terraform apply operations with
many resources can hit these limits, causing intermittent failures.

IF: apply fails with 429 or timeout errors
THEN: use -parallelism=5 (default is 10)
RUN: terraform apply -parallelism=5

IF: still hitting limits
THEN: split the apply into targeted operations
RUN: terraform apply -target=module.networking

ANTI_PATTERN: retrying immediately after rate limit error
FIX: wait 60 seconds, then retry with lower parallelism


Managed Database Deprecations

Severity: MEDIUM

UpCloud has deprecated Redis managed database in favour of Valkey.
Legacy object storage resources have also been removed from the provider.

IF: Terraform plan shows removal of upcloud_managed_database_redis
THEN: this is expected — migrate to Valkey or self-managed Redis
CHECK: GE uses self-managed Redis (port 6381), not UpCloud Managed Database for Redis

IF: references to upcloud_object_storage (legacy) exist
THEN: migrate to upcloud_managed_object_storage (new)

ADDED_FROM: provider-5.0-changelog-2026-01, deprecated resources removed


Workspace Confusion

Severity: HIGH

Applying production config in the wrong workspace can create resources
in the wrong zone or overwrite existing infrastructure.

CHECK: verify workspace before every terraform plan and terraform apply
RUN: terraform workspace show

IF: shell prompt does not show current workspace
THEN: add workspace to your prompt or use a wrapper script

ANTI_PATTERN: running terraform apply without checking workspace first
FIX: make workspace verification part of your muscle memory

ADDED_FROM: near-miss-wrong-workspace-2026-02, almost applied prod to dev


Cross-References

READ_ALSO: wiki/docs/stack/terraform-upcloud/index.md
READ_ALSO: wiki/docs/stack/terraform-upcloud/patterns.md
READ_ALSO: wiki/docs/stack/terraform-upcloud/upcloud-resources.md
READ_ALSO: wiki/docs/stack/terraform-upcloud/checklist.md