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).
Kubernetes with Ansible (recommended)
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(thecomponents.dapr.ioCRD anddapr-operator)install_redis(theredis-primaryService)
Deploy
From the cluster repo (kubernetes/cluster-medusa):
cd cluster-medusa
./install_virtufin_backend
The role:
- Asserts the Dapr, Redis, and Traefik prerequisites.
- Downloads the pinned
virtufin-<version>.tgzfrom the registry with auth (the index points chart URLs at a different Gitea hostname, so a plainhelm repo addwould strip credentials). - Applies the chart into namespace
virtufin. - Removes any pre-DaemonSet manager Deployments before the chart creates the DaemonSets (a one-time short interruption).
- 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/api—3/3ready.daemonset/workmanager— one ready pod on every worker node.daemonset/websocketmanager— one ready pod on every worker node.- No
workmanager/websocketmanagerDeployments remain after the DaemonSet migration.
Each pod runs 2/2: the service container plus its Dapr sidecar.