DigitalOcean
DigitalOcean
This guide walks through deploying Styrmin on a DigitalOcean Kubernetes cluster (DOKS), wiring it up with two DigitalOcean Load Balancers (one for the Styrmin control plane, one for application traffic) and a DigitalOcean Spaces bucket for Velero backups. At the end you'll deploy Infrahub and Semaphore through Styrmin and reach them via hostname-based ingress.
Architecture
┌────────────────────────────────────────┐
│ DigitalOcean Kubernetes (DOKS) │
│ │
demo.styrmin.app │ ┌──────────┐ ┌──────────────────┐ │
──FRONT_LB──────► │ │ Traefik │ ─► │ styrmin-server │ │
│ │ (front) │ │ + agent + prefect│ │
│ └──────────┘ └──────────────────┘ │
│ │
*.styrmin.io │ ┌──────────┐ ┌──────────────────┐ │
──APPS_LB───────► │ │ Traefik │ ─► │ Infrahub / │ │
│ │ (apps, │ │ Semaphore / … │ │
│ │ per-env) │ │ deployments │ │
│ └──────────┘ └──────────────────┘ │
│ │
│ DO Spaces (Velero backups) │
└────────────────────────────────────────┘
- The front load balancer terminates traffic to
demo.styrmin.appand routes it to the Styrmin GraphQL API and frontend. - The apps load balancer terminates wildcard traffic to
*.styrmin.ioand routes it to each deployment's own Traefik ingress controller. Styrmin provisions one dedicated Traefik per environment. - Velero backs every deployment in the environment up to a single DigitalOcean Spaces bucket.
Prerequisites
You need the following on your PATH:
doctl— DigitalOcean CLIhelm≥ 3.xkubectl- Docker (for building the Styrmin image)
uv(for runningstyrminctl)
You also need:
- A DigitalOcean account with billing enabled.
- A registered domain (this guide uses
demo.styrmin.appfor the control plane and*.styrmin.iofor applications — substitute your own). - A container registry that DOKS can pull from. The README uses an OVH Cloud registry; any public or DOKS-reachable private registry works.
1. Authenticate with DigitalOcean
doctl auth init
2. Create a Spaces bucket for Velero backups
Velero stores backup data in an S3-compatible object store. Create a
Spaces bucket in the same region as your cluster (this guide uses
ams3).
-
Create the bucket — for example named
styrmin-velero— from the DigitalOcean control panel. -
Create scoped Spaces access keys with
doctl:doctl spaces keys create styrmin-velero-key \
--grants 'bucket=styrmin-velero;permission=readwrite'
Record the returned Access Key and Secret Key. You'll need them in step 11.
3. One-time infrastructure setup
You'll provision two load balancers and one Kubernetes cluster. The load
balancers are created before the cluster so their IDs can be passed in
when Traefik is installed — DOKS will then adopt them via the
kubernetes.digitalocean.com/load-balancer-id annotation rather than
provisioning a new one per Service.
3a. Front load balancer (Styrmin control plane)
doctl compute load-balancer create \
--name ams3-load-balancer-styrmin-front \
--region ams3 \
--size lb-small \
--forwarding-rules entry_protocol:tcp,entry_port:80,target_protocol:tcp,target_port:80 \
--health-check protocol:http,port:80,path:/,check_interval_seconds:10,response_timeout_seconds:5,healthy_threshold:5,unhealthy_threshold:3
Record the returned ID and IP — referred to as <FRONT_LB_ID>
and <FRONT_LB_IP> below.
3b. Apps load balancer (application ingress)
doctl compute load-balancer create \
--name ams3-load-balancer-styrmin-apps \
--region ams3 \
--size lb-small \
--forwarding-rules entry_protocol:tcp,entry_port:80,target_protocol:tcp,target_port:80 \
--health-check protocol:http,port:80,path:/,check_interval_seconds:10,response_timeout_seconds:5,healthy_threshold:5,unhealthy_threshold:3
Record the returned ID and IP — referred to as <APPS_LB_ID>
and <APPS_LB_IP> below.
3c. DNS
Configure the following DNS records at your registrar:
| Record | Type | Target |
|---|---|---|
demo.styrmin.app | A | <FRONT_LB_IP> |
*.styrmin.io | A | <APPS_LB_IP> |
If you use your own hostnames, substitute them in every subsequent step (including step 9 where the FQDN suffix is recorded on the Cluster).
3d. Kubernetes cluster
doctl kubernetes cluster create styrmin-demo \
--region ams3 \
--version 1.35.1-do.0 \
--node-pool "name=worker-pool;size=s-2vcpu-4gb;count=2"
doctl automatically writes the kubeconfig and switches your context to
the new cluster.
4. Connect to the cluster
kubectl get nodes # verify connectivity
You should see two Ready nodes.
5. Build and push the Styrmin image
DOKS needs to be able to pull styrmin:latest from a registry it can
reach. Substitute your own registry URL in the command below — the
example uses an OVH container registry:
docker buildx build \
--platform linux/amd64 \
-t <registry>/opsmill/styrmin:latest \
-f docker/Dockerfile \
--push .
If the registry is private, also create an imagePullSecret in the
styrmin namespace and reference it from demo/do-values.yaml.
6. Install Traefik (front ingress controller)
Install Traefik as the cluster-wide ingress controller, reusing the front load balancer you created in step 3a:
helm repo add traefik https://traefik.github.io/charts
helm repo update
helm upgrade --install traefik traefik/traefik \
--create-namespace \
-n styrmin \
--set service.annotations."kubernetes\.digitalocean\.com/load-balancer-id"=<FRONT_LB_ID> \
--set ingressClass.enabled=false \
--set rbac.namespaced=true \
--wait \
--timeout 3m
The load-balancer-id annotation tells the DigitalOcean cloud
controller to attach this Service to the existing load balancer rather
than provisioning a new one.
7. Install Styrmin via Helm
helm dependency update helm/agent
helm dependency update helm/styrmin
helm upgrade --install styrmin helm/styrmin \
--dependency-update \
--create-namespace \
-n styrmin \
-f demo/do-values.yaml \
--wait \
--timeout 5m
demo/do-values.yaml is the DigitalOcean-flavoured values file (image
repository, host, ingress class, …). Review it before installing — at
minimum you'll want to point image.repository at the registry you
pushed to in step 5.
8. Access the Styrmin API
Once Helm finishes, the GraphQL API is available through the front load balancer at:
https://demo.styrmin.app/graphql
For local debugging you can still port-forward:
kubectl port-forward svc/styrmin-server 8000:8000 -n styrmin &
From here on, every step uses styrminctl
against the public URL. Export it once:
export STYRMIN_SERVER_ADDRESS=https://demo.styrmin.app
9. Create a Cluster record
Register the DOKS cluster with Styrmin. The fqdn_suffix is the wildcard
domain you configured in step 3c, and standard_storage selects the
DigitalOcean block-storage CSI driver as the default StorageClass:
uv run styrminctl clusters create do-demo \
-c '{"fqdn_suffix": "styrmin.io", "standard_storage": {"storage_class": "do-block-storage"}}'
Record the returned id — referred to as <CLUSTER_ID> below.
10. Create an Environment
The environment carries the per-environment ingress configuration. The
dedicated_ingress.service.annotations block tells the per-environment
Traefik controller (which Styrmin provisions for you) to attach to the
apps load balancer from step 3b:
uv run styrminctl environments create demo <CLUSTER_ID> \
-c '{"ip_whitelist": ["0.0.0.0/0"], "dedicated_ingress": {"service": {"annotations": {"kubernetes.digitalocean.com/load-balancer-id": "<APPS_LB_ID>"}}}}'
Record the returned id — referred to as <ENVIRONMENT_ID> below.
Tighten the IP whitelist before production.
0.0.0.0/0is open to the public internet — use a real allowlist for non-demo workloads.
11. Create a backup storage location
Register the Spaces bucket from step 2 as a backup storage location. Substitute your Spaces credentials:
uv run styrminctl backup-locations create do-spaces \
--endpoint https://ams3.digitaloceanspaces.com \
--bucket styrmin-velero \
--region ams3 \
--access-key-id <DO_SPACES_ACCESS_KEY> \
--secret-access-key <DO_SPACES_SECRET_KEY>
Record the returned id — referred to as <BSL_ID> below.
12. Assign the backup storage location to the environment
Link the BSL to the environment so every deployment in that environment backs up to the same Spaces bucket:
uv run styrminctl backup-locations assign <ENVIRONMENT_ID> <BSL_ID>
13. Load application drivers
The bundled driver examples are baked into the Docker image at
/styrmin/drivers/. Load the ones you want to deploy:
uv run styrminctl drivers load-local-version /styrmin/drivers/infrahub
uv run styrminctl drivers load-local-version /styrmin/drivers/semaphore
To load a driver from a git repository (public or private) instead, see Loading a Driver.
14. Deploy Infrahub
uv run styrminctl deployments create infrahub 1.6.0 <ENVIRONMENT_ID>
15. Deploy Semaphore
uv run styrminctl deployments create semaphore 2.16.47 <ENVIRONMENT_ID>
16. Monitor deployments
uv run styrminctl deployments get <DEPLOYMENT_ID>
You can also forward the Prefect UI for a richer view of async tasks:
kubectl port-forward svc/prefect-server 4200:4200 -n styrmin &
Then open http://localhost:4200.
17. Access applications via Ingress
Once the deployments are running, Styrmin's per-environment Traefik controller publishes them via hostname-based routing through the apps load balancer:
- Infrahub:
http://infrahub-<DEPLOYMENT_ID>.styrmin.io - Semaphore:
http://semaphore-<DEPLOYMENT_ID>.styrmin.io
Because the apps load balancer is already wired to *.styrmin.io, no
extra DNS work is needed per deployment.
Teardown
Both load balancers were created outside the Kubernetes lifecycle and
adopted by annotation, so it's safe to delete the cluster without
deleting them — but only if you tell DigitalOcean explicitly. The
service.kubernetes.io/do-loadbalancer-disown annotation does that:
# Disown the load balancers so they survive the cluster delete
kubectl annotate svc --all-namespaces -l app.kubernetes.io/name=traefik \
service.kubernetes.io/do-loadbalancer-disown="true"
Once the LBs are disowned, delete the cluster:
doctl kubernetes cluster delete styrmin-demo
To fully clean up, also delete:
- the front and apps load balancers (
doctl compute load-balancer delete <id>), - the Spaces bucket and access keys,
- the DNS records.
Troubleshooting
Helm install hangs on Traefik. The <FRONT_LB_ID> annotation must
match a load balancer that already exists in the same region as the
cluster. Run doctl compute load-balancer list to confirm.
do-block-storage PVCs stay Pending. Make sure the cluster has the
DigitalOcean block-storage CSI driver enabled (it is on by default for
DOKS ≥ 1.20). kubectl get sc should list do-block-storage as default.
Applications return 404 from *.styrmin.io. Confirm that DNS
resolution actually returns <APPS_LB_IP> (a stale *.styrmin.io record
will silently break routing) and that the per-environment Traefik
deployment is Running in the environment's namespace.
Velero backups fail with 403 SignatureDoesNotMatch. Re-check the
endpoint (https://<region>.digitaloceanspaces.com), the region
(ams3, nyc3, …), and that the access key has readwrite on the
exact bucket name.
Next steps
- Platform Overview — how the pieces fit together once Styrmin is up.
- What is an Application Driver? — understand the unit you're deploying.
- Creating a Driver — write your own driver and load it onto the cluster.