Hardening your infrastructure as code pipeline with self-hosted OpenTaco (formerly Digger)

Deploy self-hosted OpenTaco (formerly Digger) on Civo Kubernetes with the full umbrella Helm chart — orchestrator, state backend, drift detection, TLS via cert-manager, and GitHub App integration — to harden your IaC pipeline for team-scale production use.

7 minutes reading time

Written by

Jubril Oyetunji
Jubril Oyetunji

Technical Writer at Civo

In part 1 of our tutorial series, we set up a simple Digger pipeline to perform infrastructure as code (IaC) deployments, all within a GitHub pipeline. But what if we wanted to expand such a deployment for a larger installation or want it hardened for production deployments? We will look at deploying the orchestrator backend.

Utilizing the orchestrator backend will allow for efficient management of CI pipelines, especially for teams working on multiple PRs simultaneously. For this reason, this tutorial will dive into configuring and deploying the Digger orchestrator backend, setting up secure webhooks using cert-manager, and integrating it with GitHub to manage larger infrastructure environments efficiently.

A note on the rebrand: Digger is now OpenTaco

If you followed part 1, the self-hosting story has changed since then, so it is worth a short orientation before we start.

Digger has been rebranded to OpenTaco. The single digger-backend service you may have deployed before is now called the orchestrator, and it is one of several services that make up the platform. The old standalone chart still exists in the docs, but it is filed under deprecated installs. The current, supported way to self-host is an umbrella Helm chart that deploys the whole platform together.

The commands and architecture in this guide reflect OpenTaco as it stands today. Where you see the name Digger in a service image or an environment variable, that is the orchestrator's heritage showing through.

What is the orchestrator backend?

The orchestrator backend is a service that triggers Pipeline Runs. Events within the source code management system usually trigger most CI pipeline runs, code commits, pull requests, and other internal activity. Most CI systems also provide a way for CI activities to be triggered externally. This is what the orchestrator uses to help build a configuration that will work more efficiently with multiple users and larger environments.

Some of the other core points include:

  • Quicker response to PR comments and faster status check updates
  • Parallelization where appropriate
  • Appropriate queuing with multiple PRs
  • PR level locks

In OpenTaco the orchestrator is no longer the whole story. It is the piece that talks to GitHub, but it now sits alongside services for state management, drift detection, token issuance, and remote runs.

The OpenTaco architecture

Before we install anything, here is what the umbrella chart actually deploys, so the moving parts are not a surprise.

  • UI: The public entrypoint. It serves the dashboard and proxies the backend APIs.
  • Orchestrator: Processes GitHub webhooks and creates Terraform or OpenTofu workflow runs. This is the former digger-backend.
  • Drift: Runs drift detection and sends notifications.
  • Statesman: A Terraform Cloud compatible state backend, backed by S3-compatible object storage.
  • Token service: Issues API tokens for Statesman authentication.
  • Sidecar: Manages remote run sandboxes. Optional, and we leave it off in this guide.

Three of these services need a database, and Statesman needs object storage. The platform reference chart below provides both, so you have a working baseline to build on.

Two third-party dependencies are worth knowing up front. OpenTaco uses WorkOS for authentication, so you will need a WorkOS account to log in. It also uses GitHub for PR automation, which we wire up near the end.

Prerequisites

Creating a Civo Kubernetes cluster

We will provision a small cluster from the command line. Two medium nodes are enough for the whole platform:

civo kubernetes create taco-demo \
--size g4s.kube.medium \
--nodes 2 \
--create-firewall \
--wait --save --switch

The --save --switch flags merge the new kubeconfig and make it your active context. Confirm the nodes are ready:

kubectl get nodes

Every Civo cluster gets a DNS name of the form <cluster-id>.k8s.civo.com. We use it as the public URL for OpenTaco, so record it now:

civo kubernetes show taco-demo
# Note the "DNS A record" value, then:
export HOST=<your-cluster-id>.k8s.civo.com

Installing the platform baseline

OpenTaco ships an optional platform reference chart that provisions Traefik for ingress, MinIO for object storage, and a CloudNativePG PostgreSQL cluster with the three databases the services need. Treat it as a reference baseline, not a production-hardening blueprint. For a real deployment you would bring your own managed database and object storage, and on Civo you could point Statesman at Civo object storage instead of MinIO.

Create the namespaces:

kubectl create namespace opentaco
kubectl create namespace traefik

Install the platform reference chart in two steps. The chart bundles the CloudNativePG operator and a PostgreSQL cluster resource in one release, so a single install fails on CRD ordering. Install with the database disabled first, let the operator come up, then enable it:

cat > platform-values.yaml <<'EOF'
cnpg:
nameOverride: cloudnative-pg
postgresql:
enabled: false
EOF
helm upgrade --install opentaco-platform-reference \
oci://ghcr.io/diggerhq/helm-charts/opentaco-platform-reference \
--version 0.1.0 -n opentaco -f platform-values.yaml
kubectl rollout status deployment/opentaco-platform-reference-cloudnative-pg \
-n opentaco --timeout=180s
sed -i '' 's/enabled: false/enabled: true/' platform-values.yaml
helm upgrade opentaco-platform-reference \
oci://ghcr.io/diggerhq/helm-charts/opentaco-platform-reference \
--version 0.1.0 -n opentaco -f platform-values.yaml

Gotcha: the cnpg.nameOverride: cloudnative-pg value above is not optional. Without it, the bundled CloudNativePG operator cannot find its own deployment during startup and crash-loops. See the tracking note at the end of this guide.

Wait for the PostgreSQL cluster and MinIO to report ready:

kubectl get pods -n opentaco
kubectl get cluster.postgresql.cnpg.io -n opentaco

Setting up cert-manager

As we will expose the UI on a public address over HTTPS, we need a signed TLS certificate. To configure one, leverage the cert-manager Civo Marketplace app. The core cert-manager is installed, but we still need to configure it:

civo kubernetes applications add cert-manager --cluster taco-demo

The following will set up a cluster-issuer to allow us to continue:

kubectl apply -f - <<EOF
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
email: your-email@example.com
preferredChain: ""
privateKeySecretRef:
name: prod-letsencrypt-key
server: https://acme-v02.api.letsencrypt.org/directory
solvers:
- selector: {}
http01:
ingress:
class: traefik
EOF

Configuring WorkOS authentication

WorkOS must be configured before anyone can sign in. In the WorkOS dashboard, enable AuthKit, then set the following against your application, substituting your cluster host:

  • Redirect URI: https://${HOST}/api/auth/callback
  • Sign-out redirect: https://${HOST}/logout
  • CORS allowed origin: https://${HOST}
  • A webhook for the user.created event pointing at https://${HOST}/api/auth/workos/webhooks

Then collect three values you will need for the secrets below: the Client ID, an API key, and the webhook signing secret.

Creating the application secrets

OpenTaco reads its configuration from Kubernetes Secrets. Rather than putting values inline in the chart, we pre-create the secrets and reference them, which keeps credentials out of your values file and works cleanly with tools like the External Secrets Operator later.

Generate the shared internal secrets once and create the UI, orchestrator, statesman, and drift secrets. The stringData fields below take plain text and the API server encodes them:

INTERNAL_SECRET=$(openssl rand -base64 36)
ENCRYPTION_SECRET=$(openssl rand -base64 24)
STATE_KEY=$(openssl rand -base64 36)
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata: { name: ui-secrets, namespace: opentaco }
type: Opaque
stringData:
WORKOS_CLIENT_ID: "<your-workos-client-id>"
WORKOS_API_KEY: "<your-workos-api-key>"
WORKOS_WEBHOOK_SECRET: "<your-workos-webhook-secret>"
WORKOS_COOKIE_PASSWORD: "$(openssl rand -base64 24)"
WORKOS_REDIRECT_URI: https://${HOST}/api/auth/callback
PUBLIC_URL: https://${HOST}
ALLOWED_HOSTS: ${HOST},localhost
ORCHESTRATOR_BACKEND_URL: http://opentaco-taco-orchestrator-web:3000
ORCHESTRATOR_BACKEND_SECRET: ${INTERNAL_SECRET}
STATESMAN_BACKEND_URL: http://opentaco-taco-statesman:8080
TOKENS_SERVICE_BACKEND_URL: http://opentaco-taco-token-service:8081
DRIFT_REPORTING_BACKEND_URL: http://opentaco-taco-drift:3004
EOF

NOTE: Set these values without surrounding quotes. If you build the secret from one of the shipped .env example files with kubectl create secret --from-env-file, the quotes in those files are kept literally, and the UI fails to start with Failed to parse URL.

Create three more secrets the same way, named taco-orchestrator-secrets, statesman-secrets, and drift-secrets, wiring each service to its database in the shared PostgreSQL cluster. The full set of variables is listed in the OpenTaco configuration docs. One consistency rule matters most: the orchestrator's DIGGER_INTERNAL_SECRET must equal the ORCHESTRATOR_BACKEND_SECRET we just set in ui-secrets, which is why we generated INTERNAL_SECRET once and reuse it.

Installing OpenTaco

With the baseline and secrets in place, install the umbrella chart. We start from the platform-reference values, which pre-wire every service to the in-cluster PostgreSQL and MinIO, then layer our own overrides on top. Helm deep-merges values files in the order they are passed, so the two files stay cleanly separated.

Fetch the platform-reference values from the OpenTaco repo:

curl -sO https://raw.githubusercontent.com/diggerhq/digger/develop/self-hosting/kubernetes/helm-charts/opentaco/values.platform-reference.yaml

Then create values-opentaco.yaml, which points each service at its pre-created secret, sets the public URL, and enables the UI ingress:

taco-orchestrator:
digger:
secret:
useExistingSecret: true
existingSecretName: taco-orchestrator-secrets
taco-statesman:
taco:
publicBaseUrl: https://<your-cluster-id>.k8s.civo.com
secretKey: <generate with: openssl rand -base64 36>
secret:
useExistingSecret: true
existingSecretName: statesman-secrets
taco-drift:
drift:
secret:
useExistingSecret: true
existingSecretName: drift-secrets
taco-sidecar:
enabled: false
taco-ui:
ui:
secret:
useExistingSecret: true
existingSecretName: ui-secrets
ingress:
enabled: true
className: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: <your-cluster-id>.k8s.civo.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: opentaco-ui-tls
hosts:
- <your-cluster-id>.k8s.civo.com

Install:

helm upgrade --install opentaco \
oci://ghcr.io/diggerhq/helm-charts/opentaco \
--version 0.1.2-public \
-f values.platform-reference.yaml \
-f values-opentaco.yaml \
-n opentaco

Watch the pods settle:

kubectl get pods -n opentaco

NOTE: At the time of writing, the Statesman and Token service charts do not pull every database and storage variable into the pod environment, so those two services may need the OPENTACO_QUERY_BACKEND, OPENTACO_POSTGRES_*, and OPENTACO_S3_* values set directly on the deployment before they start cleanly. This is a chart bug, tracked in the note at the end.

Once all pods are running, the cert-manager certificate is issued and the UI is reachable over HTTPS:

curl -sI "https://${HOST}/"

Open https://${HOST} in a browser and you are met with the OpenTaco sign-in screen.

Installing OpenTaco

The OpenTaco landing and sign-in page, served over HTTPS on the Civo cluster.

Sign up the first user through the WorkOS-backed login, and you land on the dashboard.

Installing OpenTaco

The OpenTaco dashboard after signing in, with the Units, Repos, Projects, and Drift sections in the sidebar.

Configuring GitHub integration

The orchestrator processes GitHub webhooks, so it needs a GitHub App whose events point at your cluster. A self-hosted deployment creates its own app rather than installing the hosted one, so that the app's webhooks reach your orchestrator and its credentials belong to you.

The intended flow is to open the repository setup wizard in the dashboard, click "Connect with GitHub", and complete the GitHub App creation.

Configuring GitHub integration

The repository setup wizard: connect GitHub, add the workflow, add digger.yml, then Terraform.

You then copy the five values the app produces into your orchestrator and drift secrets:

  • GITHUB_APP_ID
  • GITHUB_APP_CLIENT_ID
  • GITHUB_APP_CLIENT_SECRET
  • GITHUB_APP_PRIVATE_KEY_BASE64
  • GITHUB_WEBHOOK_SECRET

After saving them, restart the orchestrator and drift services so the new credentials load, then install the app on the repositories you want Digger to manage.

Running Digger in the pipeline

Once the GitHub App is connected, configure Digger in the pipeline to use your self-hosted orchestrator. It is the same as in part 1, with one parameter added for the orchestrator URL:

- name: digger run
uses: diggerhq/digger@latest
with:
digger-hostname: https://<your-cluster-id>.k8s.civo.com

Cleaning up

A running cluster costs money, so remove it when you are done:

civo kubernetes remove taco-demo

If this was a throwaway run, also tidy the external accounts: delete the GitHub App from your organization settings and remove the WorkOS application or environment you configured.

Summary

You have now self-hosted OpenTaco, the platform Digger became. We deployed the orchestrator and its companion services with the umbrella chart, gave them a database and object storage with the platform reference chart, secured the UI with cert-manager and a Let's Encrypt certificate, and wired authentication through WorkOS.

Now that the platform is running, you can explore options for scaling and hardening this setup:

  • The database and object storage here run inside Kubernetes as a reference baseline. For critical environments, move PostgreSQL and state storage onto managed, backed-up infrastructure. On Civo, Statesman can use Civo object storage for state.
  • Keep all secrets in a secret manager rather than inline values, and treat the shared internal secrets as a single rotation unit.
Jubril Oyetunji
Jubril Oyetunji

Technical Writer at Civo

Jubril Oyetunji is a DevOps engineer and technical writer with a strong focus on cloud-native technologies and open-source tools. His work centers on creating practical tutorials that help developers better understand platforms such as Kubernetes, NGINX, Rust, and Go.

As a contract technical writer, Jubril authored an extensive library of technical guides covering cloud-native infrastructure and modern development workflows. Many of his tutorials achieved strong search rankings, helping developers around the world learn and adopt emerging technologies.

View author profile