# hcloud-cloud-controller-manager: the datacenter field that broke deployments

> The Hetzner Cloud cloud-controller-manager gives a Kubernetes cluster its cloud integration, from node addresses to load balancers. It is also a component that hard crashes on an outdated provider API, so version pinning is the first operational decision.

**hetznercloud/hcloud-cloud-controller-manager** — Kubernetes cloud-controller-manager for Hetzner Cloud

- Repository: https://github.com/hetznercloud/hcloud-cloud-controller-manager
- Stars: 932 · Forks: 140
- Language: Go
- License: Apache-2.0
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/hetznercloud-hcloud-cloud-controller-manager

## What the controller manager takes over in a Hetzner cluster

A Kubernetes cluster on a bare metal provider needs something to answer the questions the control plane cannot answer on its own: what is this node's address, which server backs it, how do I create a load balancer, where do I attach a route. A cloud-controller-manager answers them by talking to the provider's API. The Hetzner variant of that component is written in Go and integrates a cluster with the Hetzner Cloud and Robot APIs, and the repository ships the pieces needed to operate it: a chart directory, a deploy directory, a docs tree holding a quick start guide and a configuration reference, and a Dockerfile that produces the container image. The audience is operators running Kubernetes on Hetzner Cloud who want cloud integration without carrying a full in-tree cloud provider inside the cluster.

## The removed datacenter field is a crash, not an error

One failure mode deserves reading twice, because it takes a component down rather than degrading it. The server.datacenter field was deprecated and has been removed from the Hetzner Cloud API response since 2026-07-01. Deployments of the controller manager that were not updated before the field was removed will panic and crash, which the documentation states outright. The stated fix is to move to at least v1.30.1 if you are on version v1.30.0 or older. This shows the shape of provider API deprecations in general: the client does not receive an error it can handle, it meets an unexpected shape in a response and the process dies. A controller manager translates API changes into cluster events, so an outdated copy of it is a liability in the cluster rather than a convenience.

## No floating tags since the latest image was deleted

Tagging policy is the other operational trap, and it was fixed by removal rather than repair. On 2026-07-07 the project deleted the latest image tag, because it was broken and heavily outdated, last updated three years ago. The advice is to pin a deployment to a specific released version and to consult the releases list for what exists. The recent history shows why pinning matters here: v1.37.0 on 2026-09-11, v1.38.0 on 2026-10-01 and v1.39.0 on 2026-10-06, three releases inside a month, with the last push to the main branch on 2026-10-22. Whether that pace suits you is a separate question, but a floating tag was never a sensible choice for a component that has to survive API changes.

## Annotations and environment variables with a generated reference

Configuration reaches the controller manager through Kubernetes annotations on objects and through environment variables, and the reference documentation for both is generated rather than hand written. A custom tool, tools/doc_generation.go, reads the docstrings in the source and produces the load balancer annotation reference and the load balancer environment variable reference. The docstring format holds a description that starts with the constant name, followed by optional fields: Type, listing allowed values separated by a pipe symbol when the value is an enum, Default, which shows a dash when no default exists, Read-only, marking annotations the controller manager sets itself, and Internal, which keeps an entry out of the published reference. One annotation can point at another by wrapping its constant name in square brackets. The project shows the algorithm type annotation this way:

```go
// LBAlgorithmType specifies the algorithm type of the Load Balancer.
//
// Type: round_robin | least_connections
// Default: round_robin
LBAlgorithmType Name = "load-balancer.hetzner.cloud/algorithm-type"
```

So the values a user can set live in one place and the documentation follows the code.

## What the container image actually contains

There is no single install command in this repository, and the README does not pretend otherwise. It points at the quick start guide at docs/guides/quickstart.md for getting started and at the configuration reference at docs/reference/README.md for the available configuration. The packaging side is in the tree, with a chart directory for Helm based deployment and a deploy directory for manifests. The Dockerfile assembles a small Alpine image:

```dockerfile
FROM alpine:3.24
RUN apk add --no-cache ca-certificates bash
COPY hcloud-cloud-controller-manager /bin/hcloud-cloud-controller-manager
ENTRYPOINT ["/bin/hcloud-cloud-controller-manager"]
```

The base is alpine:3.24 with ca-certificates and bash added, and the compiled binary is the entire payload. Because the image is Alpine based, the certificate package is what lets the component talk to a TLS protected provider API.

## The development loop runs on real Hetzner servers

The development environment is the best documented workflow in the project, and it deliberately runs on real infrastructure. The required tools are tofu, k3sup, docker and skaffold, and an HCLOUD_TOKEN has to be configured in the shell session before anything starts. The README warns that the development environment runs on Hetzner Cloud servers which will induce costs. The cluster is created, its configuration loaded into the shell, and its health checked:

```sh
make -C dev up
source dev/files/env.sh
kubectl get nodes -o wide
```

From there skaffold dev rebuilds the image, redeploys it and prints the logs on every code change, and the cleanup step matters as much as the setup:

```sh
skaffold dev
make -C dev down
```

Unit tests run with the standard Go tool, and the end to end suite carries its own build tag:

```sh
go test ./...
go test ./tests/e2e -tags e2e -v
```

## Robot support needs its own account and its own test tags

Robot support sits on a separate track. Working on it requires a Robot server in the same account used for the development environment, set up with the Ansible Playbook at dev/robot/install.yml and configured in that same file. Two environment variables switch it on:

```shell
export ROBOT_ENABLED=true

export ROBOT_USER=<Your Robot User>
export ROBOT_PASSWORD=<Your Robot Password>
```

The setup then continues as usual until the skaffold step, where the robot profile is used instead:

```sh
skaffold dev --profile=robot
```

The Robot test suite is separate again and adds a second tag:

```sh
go test ./tests/e2e -tags e2e,robot -v
```

So the end to end suite runs in two flavours, the plain one with the e2e tag and the Robot one with e2e and robot, and anyone working on Robot support needs both the credentials and the Ansible setup in place before either passes.

## The dependency set ties this to one Kubernetes version

The dependency list explains how tightly the component tracks Kubernetes. go.mod pins k8s.io/api, apimachinery, client-go, cloud-provider and component-base all at version v0.37.1, alongside the Hetzner clients hcloud-go/v2 at v2.52.0 and hrobot-go at v0.2.7, with the Prometheus client library for metrics. The module requires Go 1.26.0 with toolchain go1.27.2. That coupling is the real upgrade cost: a Kubernetes version bump on the cluster side is a coordinated change here, and the project tracks upstream releases closely, which is why its release numbers follow the Kubernetes minor version. The licence field on the repository says Apache-2.0 and the README closes with the Apache License, Version 2.0 text, so licensing adds nothing to the checklist beyond keeping the notice.

## Conclusion

Adopt hcloud-cloud-controller-manager when you run Kubernetes on Hetzner Cloud and want node, load balancer and route handling backed by the provider API instead of an in-tree provider, and pin every deployment to a specific released version. Do not deploy a copy older than v1.30.1, because the removal of the server.datacenter field on 2026-07-01 makes those builds panic, and do not rely on a floating tag, since the latest image tag was deleted on 2026-07-07. Verify first that your manifest names an explicit version, that the cluster's Kubernetes version matches the k8s.io v0.37.1 dependencies in go.mod, and that the annotations you use exist in the generated reference.

## FAQ

### Which version of hcloud-cloud-controller-manager should I run?

At least v1.30.1. The server.datacenter field was removed from the Hetzner Cloud API response on 2026-07-01, and deployments on version v1.30.0 or older that were not updated will panic and crash. Pin the deployment to a specific released version, since the latest image tag was deleted on 2026-07-07.

### How do I set up a development environment for hcloud-cloud-controller-manager?

Install tofu, k3sup, docker and skaffold, configure an HCLOUD_TOKEN in your shell session, then run make -C dev up, source dev/files/env.sh and check the cluster with kubectl get nodes -o wide. Use skaffold dev to iterate and make -C dev down to tear it down, keeping in mind that the development servers cost money.

### How do I run the end to end tests for hcloud-cloud-controller-manager?

The Kubernetes end to end suite needs the development environment first, then the command go test ./tests/e2e -tags e2e -v after sourcing dev/files/env.sh. For the Robot suite, export ROBOT_ENABLED, ROBOT_USER and ROBOT_PASSWORD, configure dev/robot/install.yml, and run go test ./tests/e2e -tags e2e,robot -v.

### How is the load balancer algorithm type configured in hcloud-cloud-controller-manager?

Through the load-balancer.hetzner.cloud/algorithm-type annotation, whose constant is named LBAlgorithmType. Its documented type is round_robin or least_connections, and the default is round_robin.

### Where is the configuration reference for hcloud-cloud-controller-manager?

The README points at the quick start guide in docs/guides/quickstart.md and the configuration reference in docs/reference/README.md. The load balancer annotation and environment variable references are autogenerated by a tool in tools/doc_generation.go.

## Sources

- [hetznercloud/hcloud-cloud-controller-manager on GitHub](https://github.com/hetznercloud/hcloud-cloud-controller-manager)
- [Issues](https://github.com/hetznercloud/hcloud-cloud-controller-manager/issues)
- [License: Apache-2.0](https://github.com/hetznercloud/hcloud-cloud-controller-manager/blob/main/LICENSE)
- [README](https://github.com/hetznercloud/hcloud-cloud-controller-manager/blob/main/README.md)
- [Releases](https://github.com/hetznercloud/hcloud-cloud-controller-manager/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/hetznercloud-hcloud-cloud-controller-manager
