digitalocean-cloud-controller-manager: letting Kubernetes manage DigitalOcean
Kubernetes cloud-controller-manager for DigitalOcean (beta)
At a glance
- What is it?
- This is the cloud controller manager for DigitalOcean, and the interesting engineering is not in the controllers themselves but in the reconciliation loop that keeps a DigitalOcean load balancer in step with a Kubernetes Service, which is also why the documentation spends so much time telling you not to touch the load balancer by hand.
- Who is it for?
- digitalocean-cloud-controller-manager is the right tool for a Kubernetes cluster on DigitalOcean that needs cloud load balancers, node addresses or a firewall that follows the Services in the cluster, and it is already installed for you on DOKS if that is where you run.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What a cloud controller manager does for DigitalOcean
digitalocean-cloud-controller-manager is the Kubernetes cloud controller manager implementation for DigitalOcean, and the readme links to the upstream Kubernetes documentation on running a cloud controller manager.
The point of a cloud controller manager is to move the cloud specific parts of a cluster's control loop out of the core Kubernetes components and into a separate process that talks to the provider's API. What that buys you on DigitalOcean is the ability to use the provider's own features rather than approximating them. The readme says it allows you to make use of many of the cloud provider features offered by DigitalOcean on your Kubernetes clusters, and the examples it links are load balancers and node labels and addresses, both of which are the classic cloud controller manager responsibilities.
There is a detail in the readme that settles who should be reading this. The note says this CCM is installed by default on DOKS, the DigitalOcean Managed Kubernetes offering, so you do not have to install it yourself. That makes the project interesting to two different audiences: a DOKS user who is already running it and wants to know what it does to their cluster, and an operator running Kubernetes on DigitalOcean droplets who has to install and manage it themselves.
The implementation is Go, the licence is Apache 2.0, and the repository description calls it a beta Kubernetes cloud controller manager for DigitalOcean. The topics list beta, cloud providers, controller manager, hacktoberfest and kubernetes, which matches a project that is production usable but still collecting contributors.
The reconciliation loop is why you must not edit a load balancer by hand
The production notes section contains the single most important operational rule in the readme, and it is worth understanding the mechanism rather than just obeying it.
When you create a load balancer through the CCM, by declaring a Service of type LoadBalancer, it is very important that you must not change the DigitalOcean load balancer configuration manually. Such changes will eventually be reverted by the reconciliation loop built into CCM. There is one exception, the load balancer name, which can be changed, and the documentation points to the load balancer ID annotations section for that.
The reason is the shape of a controller. The controller watches the Kubernetes API for the desired state, compares it with the state it last observed in the provider, and then makes the provider match. Anything you change in the provider by hand is, from the controller's point of view, drift, and the next reconciliation overwrites it. The loop is not a synchronisation daemon that accepts changes; it is a loop that restores its own definition of truth.
So the only safe place to change load balancer configuration is the Service object. That is a different workflow from the cloud console, and for a team used to clicking in a web UI it is the thing that has to be learned first.
The documentation also records a hard constraint that comes from the provider rather than the controller. The ports 50053, 50054 and 50055 cannot be used as load balancer entry ports, because of technical reasons on the DigitalOcean side, and trying to use one of them as a service port returns a 422 entry port is invalid HTTP error from the API, surfaced as a Kubernetes event. The fix is to change the service port to a non-conflicting one.
Optional features: the public access firewall and Prometheus metrics
Two optional behaviours are documented in detail, and both are opt in through environment variables.
The first is the public access firewall. The controller can manage a DigitalOcean Firewall that dynamically adjusts rules for accessing NodePorts: once a Service of type NodePort is created, the firewall controller updates the firewall to publicly allow access to just that NodePort, and access is automatically retracted if the Service is deleted or changed to a different type.
The configuration is by environment variable.
cd cloud-controller-manager/cmd/digitalocean-cloud-controller-manager
DO_ACCESS_TOKEN=<your_access_token> \
PUBLIC_ACCESS_FIREWALL_NAME=firewall_name \
PUBLIC_ACCESS_FIREWALL_TAGS=worker-droplet \
digitalocean-cloud-controller-manager \
--kubeconfig <path to your kubeconfig file> \
--leader-elect=false --v=5 --cloud-provider=digitaloceanThe name variable defines which firewall is used, and the firewall is created if none with that name is found. The tags variable refers to the tags of the droplets the firewall should apply to, usually a tag on the worker node droplets, and multiple tags are applied in a logical OR fashion.
There is an opt out, and it is per Service. The annotation kubernetes.digitalocean.com/firewall-managed set to "false" excludes that Service from firewall management, so no inbound rules are created and the NodePort is not publicly accessible. The quotes are required around boolean annotation values, and the default behaviour applies if the annotation is omitted, is set to "true", or contains an invalid value. No firewall is managed at all if the environment variables are missing or empty.
The second feature is a metrics endpoint, exposed by passing a metrics address.
cd cloud-controller-manager/cmd/digitalocean-cloud-controller-manager
DO_ACCESS_TOKEN=your_access_token \
METRICS_ADDR=<host>:<port> \
digitalocean-cloud-controller-manager \
--kubeconfig <path to your kubeconfig file> \
--leader-elect=false The module list in go.mod includes the Prometheus client library, so the metrics are first class rather than an afterthought.
Running it locally against a cluster
The development section explains how to run the controller outside the cluster, which is the situation where you most want it, because that is when you are changing it.
The invocation is a go run from the command directory, with the region, the access token and the cloud provider flag.
cd cloud-controller-manager/cmd/digitalocean-cloud-controller-manager
REGION=fra1 DO_ACCESS_TOKEN=your_access_token go run main.go \
--kubeconfig <path to your kubeconfig file> \
--leader-elect=false --v=5 --cloud-provider=digitaloceanThree details in that command are load bearing. The REGION environment variable takes a valid DigitalOcean region, and setting it keeps the controller from trying to reach the DigitalOcean metadata service, which is only available on droplets, so on a laptop the DO Regions service is used to validate the specified region instead. The readme also notes it can be set for local development purposes and that which region you choose should not matter much as long as you pick one.
The DO_ACCESS_TOKEN variable carries the DigitalOcean access token. The readme is precise about a behaviour that saves confusion: the token does not need to be valid for the cloud controller to start, but in that case you will not be able to validate integration with the DigitalOcean API.
The --leader-elect=false flag matters for a local run, and the readme warns about a consequence of running locally against a DigitalOcean cluster: there will already be a cloud controller manager running in the cluster, so your local one will compete for API access with it. That is worth taking seriously, since two controllers reconciling the same load balancers is a good way to produce confusing behaviour rather than a good way to test a change.
Version support, building and testing
The releases section explains the version policy, and it is the first thing to read before planning an upgrade.
Cloud controller manager follows semantic versioning, and although the version is still below v1, the project is considered production-ready. The compatibility rule follows from Kubernetes' own release cycle: the CCM will only support the version that is also supported on the DigitalOcean Kubernetes product, and any other releases will not be officially supported.
In practice that means a fixed pairing. You run the controller release that matches the Kubernetes version DigitalOcean supports, and you upgrade them together rather than independently. The release history shows how that works in practice, with v0.1.69 on 2026-08-25, v0.1.70 on 2026-09-11 and v0.1.71 on 2026-09-17, so the patch cadence is quick and the numbers stay in the zero series.
On the build side, the project needs Go with a minimum of v1.25.x according to the readme, while the go.mod file itself declares go 1.27.0, and the dependency list is pinned to the Kubernetes libraries at v0.37.0, including api, apimachinery, client-go, cloud-provider and component-base, plus the DigitalOcean godo client at v1.208.0. The repository uses Go modules and employs vendoring, and the readme asks you to run make vendor after any dependency modification.
The check command for a change is short.
make ciThe Makefile itself derives the version from a VERSION file, injects it and the commit into the binary with linker flags, detects a dirty git tree, and exposes an envtest target pinned to a Kubernetes version, with an envtest directory and an e2e directory at the top level alongside the cloud-controller-manager source, the ci directory, the releases directory and the scripts.
Where the DigitalOcean cloud controller manager is the wrong tool
Four limits, and they are mostly consequences of the design rather than defects.
The first is that it only works on DigitalOcean. It talks to the DigitalOcean API through the godo client, and the firewall and load balancer behaviour is specific to that provider's resources. A multi-cloud cluster needs one controller per provider, and the components are not interchangeable.
The second is the version coupling. Supporting only the Kubernetes version that DigitalOcean itself supports means the controller cannot be upgraded ahead of the managed offering, and a self managed cluster on droplets has to accept that schedule whether or not it suits them.
The third is the provider port restriction. The ports 50053, 50054 and 50055 cannot be used as load balancer entry ports, and the failure arrives as an API error surfaced as a Kubernetes event rather than as a validation failure at apply time. An application that happens to need one of those ports has to move, and the documentation is the only place that says so.
The fourth is the firewall behaviour, which is convenient by default and has a sharp edge. Once a firewall exists, no public access other than to the NodePorts is allowed, and the readme says users should create additional firewalls to extend access further. A team that did not expect its node ports to be the only public entry point will find that out from the firewall, not from the controller.
And the fifth, which is a workflow rather than a feature: because the reconciliation loop restores the controller's idea of the truth, anyone who wants a load balancer configured differently has to express that in the Service, not in the DigitalOcean console.
Licence, project status and what to check first
The licence is Apache 2.0, with the licence file at the root of the repository, and the Makefile carries the matching Apache 2.0 header. That is a permissive grant with an express patent clause, and it is a common choice for a component that runs inside someone else's cluster. This is not legal advice.
The status of the project is described in two places that say slightly different things, and both are worth knowing. The repository description calls it a beta, the topics include beta, and the hacktoberfest topic suggests it is actively collecting outside contributions. The releases section says that although the version is still below v1, the project is considered production-ready. The practical reading is that the code is trusted in production while the version numbering and the API surface are still settling.
The last push was on 2026-10-21 and the repository is not archived, so the work is current, and the changelog is kept in a CHANGELOG.md at the root alongside a VERSION file that the build reads.
For an adopter, the order of checks is what the documentation itself suggests. Read the getting started guide in docs first, because the readme is explicit that it is where the operational detail lives, and the examples for load balancers and for node labels and addresses are the two places to look for the behaviour your cluster will actually exhibit. Then pin the controller version to the Kubernetes version DigitalOcean supports, rather than the newest tag, because the older versions are the ones that are not officially supported. Then decide deliberately about the public access firewall, because it is enabled by the presence of two environment variables and its absence is just as consequential as its presence.
Editorial conclusion
digitalocean-cloud-controller-manager is the right tool for a Kubernetes cluster on DigitalOcean that needs cloud load balancers, node addresses or a firewall that follows the Services in the cluster, and it is already installed for you on DOKS if that is where you run. It is the wrong tool for a cluster on another provider, for anyone who wants to manage the load balancer outside Kubernetes, and for a team unwilling to accept that the controller and the DigitalOcean API are in a reconciliation loop. Before adopting, read the getting started guide in docs, check the version pairing between the controller and your Kubernetes release, and set the firewall management annotation to false on any Service that should stay inside the VPC.
Frequently asked questions
What does the DigitalOcean cloud controller manager do?
It is the Kubernetes cloud controller manager implementation for DigitalOcean, which lets a cluster use DigitalOcean's own cloud features rather than approximations of them. The documented examples are load balancers and node labels and addresses, and the controller is installed by default on DOKS, the DigitalOcean Managed Kubernetes offering.
Why should I not modify a DigitalOcean load balancer manually?
Because the controller runs a reconciliation loop that will revert any manual change, since the Service object is the only safe place to configure a load balancer. The load balancer name is the one exception, and the documentation points at the load balancer ID annotations for that.
How do I run the DigitalOcean cloud controller manager locally?
From the cloud-controller-manager/cmd/digitalocean-cloud-controller-manager directory, run go run main.go with REGION set to a valid DigitalOcean region, DO_ACCESS_TOKEN set to your token, and the flags --kubeconfig, --leader-elect=false, --v=5 and --cloud-provider=digitalocean. The readme warns that a controller already running in a DigitalOcean cluster will compete with your local one for API access.
Which Kubernetes versions does the DigitalOcean cloud controller manager support?
Only the version that is also supported on the DigitalOcean Kubernetes product, because of the fast Kubernetes release cycles, and any other releases are not officially supported. The project follows semantic versioning, is still below v1, and is considered production-ready regardless.
How do I stop a NodePort Service from being opened to the internet?
Set the Service annotation kubernetes.digitalocean.com/firewall-managed to "false", with the quotes included, and no inbound rules will be created for that Service. The default behaviour applies when the annotation is omitted, is set to "true", or contains an invalid value.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/digitalocean-digitalocean-cloud-controller-manager)Was this useful?