# Creating a Dokploy Terraform provider

> What it takes to write a Terraform provider in Go for a young API - and why the acceptance tests talk to a real server.

- date: 2026-08-22
- tags: go, terraform, dokploy

I wanted `terraform apply` to be the only deploy button on my
personal infrastructure. Dokploy had an API and no living provider,
so now there is `vanillauys/dokploy`: written in Go on the
terraform-plugin-framework, at v0.10.0 after ten minor releases.

The shape is the standard one. A thin API client package that knows
nothing about Terraform, and a resource layer that maps schema to
client calls:

```go
func (r *ProjectResource) Create(ctx context.Context,
	req resource.CreateRequest, resp *resource.CreateResponse) {
	var plan ProjectModel
	resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)

	project, err := r.client.CreateProject(ctx, plan.Name.ValueString())
	// map the response back into state, nothing clever
}
```

The framework does the heavy lifting: plan diffs, state upgrades,
unknown-value propagation. Your job is to be honest about the API you
are wrapping.

## Test against the real thing

The acceptance suite runs against an actual Dokploy instance in CI on
every pull request - a container on the runner's own Docker, no
docker-in-docker. Mocks would have passed builds that failed against
the real server, because a young API has opinions the docs do not
mention. Every one of these came out of a failing acceptance test,
not a bug report:

- Computed collections need `UseStateForUnknown`, or every plan
  recomputes them and downstream references cascade into unknowns.
  Where the API recomputes them anyway, the answer is a data source
  and documentation, not wishful state.
- Some deletes are asynchronous. `Delete` has to poll until the
  resource is actually gone, or the next test's `Create` collides
  with a name that still exists.

## Releasing is a checklist, not a ceremony

The Terraform registry wants tagged releases with signed artifacts
and a manifest; goreleaser produces the matrix and the registry
ingests each tag within minutes. After that, releasing often is
cheap - six of the ten minors shipped in single-day bursts as the
resource surface grew.

v1.0 is deliberately not tagged yet. The rule: the provider must
survive one Dokploy minor upgrade without breaking changes first. A
1.0 is a promise about the future, and I want at least one data point
before making it.