Skip to content

Operations

How to run and deploy Virtufin — from a laptop to a Kubernetes cluster. Pick the mode that matches what you are doing; the services and their contracts are identical in all of them. The normative contract lives in the deployment spec.

Mode Best for Runtime
Native local Day-to-day development, debugging dapr run, .NET SDK
Docker Compose Reproducible local stack, demos Docker containers
Helm Direct/manual Kubernetes installs Kubernetes + Dapr
Ansible Production clusters Kubernetes + Dapr

All three backend services share the same shape: an HTTP port for health/REST and a gRPC port for inter-service traffic. The API Gateway is the only public face; Dapr is the only component that talks to the broker and state store (see Architecture).

Native local (deploy-local)

Runs the backend services natively via Dapr (dapr run). No service containers — Dapr manages its own sidecars, and the services run as local processes.

Prerequisites

  • Dapr CLI initialized (dapr init)
  • .NET SDK 10.0+
  • Redis on localhost:6379 (Dapr state store and pub/sub)

Start the stack

git clone https://git.haenerconsulting.com/virtufin/deploy-local.git
cd deploy-local

# All backend services
./scripts/deploy_backend.sh

# Or individual services
./scripts/deploy_workmanager.sh
./scripts/deploy_websocketmanager.sh

Stop

./scripts/undeploy_backend.sh
./scripts/undeploy_workmanager.sh
./scripts/undeploy_websocketmanager.sh

Ports and build settings live in .env. See the deploy-local README for the full variable table and the components/ Dapr definitions.

Docker Compose

Deploys the full platform — API + WorkManager + WebSocketManager plus Redis, Dapr placement, and Zipkin — as Docker containers with Dapr sidecars.

Prerequisites

  • Docker with the Compose plugin
  • Harbor registry access for the docker.haenerconsulting.com/virtufin/* images

Start the stack

git clone https://git.haenerconsulting.com/virtufin/docker-compose.git
cd docker-compose
docker compose up -d

Verify

docker compose ps
curl http://localhost:5001/health

Stop

docker compose down       # keep volumes
docker compose down -v    # remove volumes (clears Redis data)

.env next to the compose file controls image versions and host port mappings. See the docker-compose README for the full reference.

Kubernetes with Helm

The platform ships as a single chart, virtufin/virtufin, published by the virtufin/helm repo to a private Gitea Helm registry. Each service can be enabled, disabled, and versioned independently.

Prerequisites (asserted, never installed by the chart)

  • Kubernetes 1.27+
  • Dapr installed on the cluster
  • Redis/Valkey for the state store and pub/sub
  • A Harbor image pull secret

Install

The registry is private, so pass credentials when adding it:

helm repo add virtufin \
  --username <user> --password <token> \
  https://git.haenerconsulting.com/api/packages/virtufin/helm
helm repo update

helm upgrade --install virtufin virtufin/virtufin \
  --namespace virtufin --create-namespace \
  -f values.yaml

Keep credentials, API keys, and Redis hosts in a values.yaml rather than on the command line. The settings you will normally set:

Value Purpose Default
api.image.tag API Gateway image tag pinned by the release
workmanager.image.tag WorkManager image tag pinned by the release
websocketmanager.image.tag WebSocketManager image tag pinned by the release
stateStore.host / pubsub.host Redis/Valkey host redis-primary.redis.svc.cluster.local
api.auth.keys gRPC/REST API keys (name -> key) {} (auth disabled)
api.ingress.enabled / api.ingress.host Public gRPC endpoint true / api.virtufin.com
imagePullSecrets Harbor pull secret regcredharbor
workmanager.nugetAuth Gitea package credentials for worker code sources

See the Helm chart README for the complete values reference, ingress/auth detail, and the Harbor secret setup.

Workload placement

The chart uses two Kubernetes workload types:

Service Workload Scaling
API Gateway Deployment api.replicaCount (default 3)
WorkManager DaemonSet One pod per eligible worker node
WebSocketManager DaemonSet One pod per eligible worker node

WorkManager and WebSocketManager default to DaemonSets because their capacity is meant to follow the worker nodes: a new eligible worker gets a pod automatically, and draining a worker removes its pod. The default affinity excludes control-plane/master nodes and adds no control-plane tolerations.

A manager can use a fixed replica count instead, for example on a constrained or development cluster:

workmanager:
  workloadKind: Deployment
  replicaCount: 3

websocketmanager:
  workloadKind: Deployment
  replicaCount: 3

replicaCount is only rendered when workloadKind is Deployment. Changing an existing manager between Deployment and DaemonSet changes the Kubernetes resource kind, which cannot be updated in place — the old object must be deleted first (the Ansible path below does this automatically).

Production clusters are owned by Ansible. The k3s.common.virtufin_backend role in kubernetes/k3s-common pins the chart version and image tags, renders api.auth.keys from the cluster .env, applies the chart, and verifies the result. The per-service deploy-*/undeploy-* scripts and CI workflows were retired so there is a single writer for the release.

Prerequisites — install these roles first:

  • install_dapr (the components.dapr.io CRD and dapr-operator)
  • install_redis (the redis-primary Service)

Deploy

From the cluster repo (kubernetes/cluster-medusa):

cd cluster-medusa
./install_virtufin_backend

The role:

  1. Asserts the Dapr, Redis, and Traefik prerequisites.
  2. Downloads the pinned virtufin-<version>.tgz from the registry with auth (the index points chart URLs at a different Gitea hostname, so a plain helm repo add would strip credentials).
  3. Applies the chart into namespace virtufin.
  4. Removes any pre-DaemonSet manager Deployments before the chart creates the DaemonSets (a one-time short interruption).
  5. Waits for the API Deployment and both manager DaemonSets, then asserts the running images match the pinned tags.

Override a value

./install_virtufin_backend \
  --extra-vars virtufin_backend_api_image_tag=0.8.2

Uninstall

./uninstall_virtufin_backend

Secrets come from the cluster .env via group_vars: VIRTUFIN_REGISTRY_USER/TOKEN (Gitea packages), VIRTUFIN_DOCKER_USER/TOKEN (Harbor robot), and VIRTUFIN_API_AUTH_KEYS (a JSON name -> key map). The key map must be non-empty — the API's api-auth health check crash-loops the pod without at least one key.

Verify

kubectl -n virtufin get deployment,daemonset,pods -o wide
kubectl -n virtufin get events --sort-by=.lastTimestamp
helm -n virtufin status virtufin

A healthy production release:

  • deployment/api3/3 ready.
  • daemonset/workmanager — one ready pod on every worker node.
  • daemonset/websocketmanager — one ready pod on every worker node.
  • No workmanager/websocketmanager Deployments remain after the DaemonSet migration.

Each pod runs 2/2: the service container plus its Dapr sidecar.