Skip to content

Deployment Guide

Overview

Virtufin API can be deployed using Docker containers and Kubernetes. This guide covers setup for private package registries, local Docker deployment, and Kubernetes deployment.


Private Package Registries

All client libraries are published to private Gitea package registries. Configure authentication before installing.

Obtaining API Tokens

  1. Navigate to https://git.haenerconsulting.com/user/settings/applications
  2. Create a new access token with scope read:packages
  3. Store the token securely as it will be used as a password

Docker

# Authenticate
docker login docker.haenerconsulting.com -u <username>

# Pull the image
docker pull docker.haenerconsulting.com/virtufin/api:latest

# Pull backend service images
docker pull docker.haenerconsulting.com/virtufin/workmanager:latest
docker pull docker.haenerconsulting.com/virtufin/websocketmanager:latest

Credentials are stored in ~/.docker/config.json after login.


NuGet

Option 1 — NuGet.Config XML:

Create or edit ~/.nuget/NuGet/NuGet.Config:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <clear />
    <add key="gitea" value="https://nuget.haenerconsulting.com/api/packages/virtufin/nuget/index.json" />
  </packageSources>
  <packageSourceCredentials>
    <gitea>
      <add key="Username" value="<username>" />
      <add key="ClearTextPassword" value="<api-token>" />
    </gitea>
  </packageSourceCredentials>
</configuration>

Option 2 — dotnet CLI:

dotnet nuget add source https://nuget.haenerconsulting.com/api/packages/virtufin/nuget/index.json \
  -n gitea -u <username> -p <token>

Install package:

dotnet add package Virtufin.Api.Client

PyPI

Setup pip.conf:

Linux: ~/.config/pip/pip.conf macOS: ~/Library/Application Support/pip/pip.conf

[global]
index-url = https://pypi.haenerconsulting.com/api/packages/virtufin/pypi/

[install]
trusted-host = pypi.haenerconsulting.com

Install with authentication:

export PIP_USERNAME=<username>
export PIP_PASSWORD=<api-token>
pip install virtufin-api

NPM

Setup ~/.npmrc:

npm config set @gitea:registry https://npm.haenerconsulting.com/api/packages/virtufin/npm/
npm config set //npm.haenerconsulting.com/api/packages/virtufin/npm/:_authToken <token>

Install package:

npm install @virtufin/api

Local Docker Deployment

Pull and Run

# Pull the API gateway image
docker pull docker.haenerconsulting.com/virtufin/api:latest

# Run with configuration
docker run -p 5001:5001 \
           -p 5002:5002 \
           -e ServicesConfigPath=/config/services.json \
            -e HttpPort=5001 \
           -e GrpcPort=5002 \
           -e DaprHttpPort=3500 \
           -e DaprGrpcPort=50001 \
           -v /path/to/services.json:/config/services.json \
           docker.haenerconsulting.com/virtufin/api:latest

Ports

Port Protocol Description
5001 HTTP/gRPC-Web HTTP port (REST, Swagger, health, gRPC-Web)
5002 gRPC (h2c) Native gRPC HTTP/2 port

Required Environment Variables

Variable Default Description
HttpPort 5001 HTTP port (REST, Swagger, gRPC-Web)
GrpcPort 5002 gRPC port (native gRPC h2c)
DaprHttpPort 3500 Dapr sidecar HTTP port
DaprGrpcPort 50001 Dapr sidecar gRPC port
ServicesConfigPath config/services.json Path to services configuration (relative to CWD; override with the ServicesConfigPath config key)

Note: Dapr__HttpEndpoint and Dapr__GrpcEndpoint environment variables are deprecated. Use DaprHttpPort and DaprGrpcPort instead. The deploy script sets these automatically.

Important: All backend services registered in the config must implement gRPC reflection. The API Gateway discovers methods and executes calls via reflection. Services without reflection cannot be proxied.

Verify Deployment

# Health check
curl http://localhost:5001/health

# List services (requires running backend services)
curl http://localhost:5001/v1/gateway/list-services

Kubernetes Deployment

Generic Kubernetes

Prerequisites

  • Kubernetes 1.28+ cluster
  • kubectl configured with cluster access
  • Dapr CLI installed locally

Install Dapr

dapr init -k
dapr status -k

Deploy

helm install virtufin-api ../helm \
  --namespace virtufin --create-namespace \
  --set api.enabled=true

Verify

kubectl get pods -n virtufin
kubectl get svc -n virtufin
kubectl logs -n virtufin -l app.kubernetes.io/name=virtufin-api
kubectl rollout status deployment/virtufin-api -n virtufin

Local Development with Colima (macOS)

Colima provides a lightweight Kubernetes cluster for local development on macOS.

Prerequisites

  • macOS
  • Colima installed: brew install colima
  • Docker CLI (or Docker Desktop for docker context): brew install docker

Start Colima with Kubernetes

# Start Colima with Kubernetes enabled
colima start --kubernetes

# Verify kubectl context
kubectl config current-context
# Expected: colima

# Optional: configure docker to use colima context
docker context ls
docker context use colima

Install Dapr

dapr init -k
dapr status -k

Deploy

helm install virtufin-api ../helm \
  --namespace virtufin --create-namespace \
  --set api.enabled=true

Verify

kubectl get pods -n virtufin
kubectl get svc -n virtufin
kubectl logs -n virtufin -l app.kubernetes.io/name=virtufin-api
kubectl rollout status deployment/virtufin-api -n virtufin

# Access the API
curl http://localhost:5001/health

Stop Colima

colima stop

Troubleshooting

  • Pod stuck in Pending: Check kubectl describe pod <pod> -n virtufin for resource allocation issues
  • Dapr sidecar not starting: Ensure Kubernetes support is enabled with colima status
  • Context not set: Run kubectl config use-context colima

Podman Kubernetes

Podman Desktop or Podman CLI can provide a Kubernetes cluster for local development.

Prerequisites

  • Podman Desktop (recommended) or Podman CLI v4.0+
  • Linux, macOS, or Windows with WSL2

Setup with Podman Desktop

  1. Download and install Podman Desktop
  2. Open Podman Desktop → Settings → Kubernetes
  3. Enable "Enable Kubernetes" checkbox
  4. Choose single-node cluster option
  5. Click "Apply and Restart"

Setup with Podman CLI

# Check podman version (4.0+ required for k8s)
podman version

# Create a machine with Kubernetes enabled (Podman v4.1+)
podman machine init --kubernetes-enabled=true my-k8s
podman machine start my-k8s

# Or enable Kubernetes on existing machine
podman machine stop my-machine
podman machine set --kubernetes-enabled=true my-machine
podman machine start my-machine

Verify Context

kubectl config current-context
# Expected: podman

# If not set:
kubectl config use-context podman

Install Dapr

dapr init -k
dapr status -k

Deploy

helm install virtufin-api ../helm \
  --namespace virtufin --create-namespace \
  --set api.enabled=true

Verify

kubectl get pods -n virtufin
kubectl get svc -n virtufin
kubectl logs -n virtufin -l app.kubernetes.io/name=virtufin-api
kubectl rollout status deployment/virtufin-api -n virtufin

Access the API

On macOS with Colima or Podman, port forwarding may be needed:

kubectl port-forward -n virtufin svc/virtufin-api 5001:5001 5002:5002
# Then access: http://localhost:5001/health (HTTP), http://localhost:5002 (gRPC)

Stop Podman Machine

podman machine stop my-k8s

Troubleshooting

  • Kubernetes not enabled: Ensure Podman Desktop settings have Kubernetes enabled
  • Port conflicts: Ensure port 5001 is not in use
  • Image pull issues: Verify docker.haenerconsulting.com registry credentials are configured

Production Considerations

Scaling

Kubernetes:

spec:
  replicas: 3  # Scale API horizontally

Kubernetes Services handle backend load balancing automatically — set host to the Service DNS name in services.json and kube-proxy distributes calls across pods.

Non-Kubernetes (multi-instance round-robin):

For bare-metal or VM deployments, configure multiple backend instances with the hosts array. The gateway round-robins gRPC calls across all configured hosts:

{
  "services": [{
    "name": "workmanager",
    "grpc": {
      "hosts": [
        {"host": "instance-1", "port": 25002},
        {"host": "instance-2", "port": 25002}
      ]
    }
  }]
}

When hosts is omitted, the single host/port pair is used.

Resource Limits

resources:
  requests:
    memory: "128Mi"
    cpu: "100m"
  limits:
    memory: "512Mi"
    cpu: "1000m"

Health Checks

livenessProbe:
  httpGet:
    path: /health
    port: 5001
  initialDelaySeconds: 10
  periodSeconds: 15

readinessProbe:
  httpGet:
    path: /health
    port: 5001
  initialDelaySeconds: 5
  periodSeconds: 10

Security

  1. TLS Termination: Configure TLS at ingress level
  2. CORS: Restrict CORS policies in production
  3. Authentication: Add authentication layer if needed
  4. Secrets: Use Kubernetes Secrets for sensitive data

Troubleshooting

Pod Not Starting

kubectl describe pod <pod-name> -n virtufin
kubectl logs <pod-name> -n virtufin

Dapr Not Working

# Check Dapr sidecar
kubectl get pods --show-labels -n virtufin | grep dapr

# View Dapr logs
kubectl logs <pod-name> -c daprd -n virtufin

Cannot Connect to Backend Services

  1. Verify backend services are deployed and running
  2. Check DNS resolution: kubectl exec -it <pod> -n virtufin -- nslookup <service-name>
  3. Verify gRPC ports are accessible
  4. Check ConfigMap values match backend service addresses

Image Pull Failures

  1. Verify regcredharbor secret exists:
    kubectl get secret regcredharbor -n virtufin
    
  2. Recreate if missing:
    kubectl create secret docker-registry regcredharbor \
      --namespace=virtufin \
      --docker-server=docker.haenerconsulting.com \
      --docker-username=<username> \
      --docker-password=<token>
    

Health Check Failures

  1. Verify API is listening on correct port (5001)
  2. Check service has gRPC reflection enabled (mandatory for API Gateway to function)
  3. Review logs for startup errors:
    kubectl logs -f <pod-name> -n virtufin
    

Known Limitations

Dapr gRPC App Health Checks Not Supported

When using Dapr with app-protocol: grpc, the Dapr sidecar probes the application via the dapr.proto.runtime.v1.AppCallbackHealthCheck gRPC service. The Dapr .NET SDK does not currently provide a server-side implementation of this service. As a result, Dapr app health checks are disabled (dapr.io/enable-app-health-check: false).

Kubernetes liveness and readiness probes (/health endpoint) continue to handle pod health monitoring. Future improvement: implement the Dapr AppCallback gRPC service to enable Dapr's soft health check mechanism for pausing work delivery during unhealthy states.

v0.0.42 Changes

Flat JSON pubsub format

WebSocket messages published to Dapr pub/sub no longer use the MessageEnvelope wrapper. The CloudEvent data field now contains the raw WebSocket payload as a JSON object directly. Metadata (websocketId, websocketUrl, timestamp, contentType) is attached as CloudEvent extensions instead of being wrapped inside the payload.

Consumers now read data with a single json.loads() step — no base64 decoding, no .payload field indirection.

Dapr config keys

Dapr__HttpEndpoint and Dapr__GrpcEndpoint environment variables are replaced by DaprHttpPort and DaprGrpcPort. The deploy script sets these automatically. Existing dotnet run defaults remain 50001.

Multi-instance gRPC scaling

The gateway GrpcChannelPool supports round-robin across multiple backend instances via the hosts array in services.json (see Scaling section above). In Kubernetes, a single Service DNS name with replicas is the recommended approach — hosts is for non-K8s deployments.

WorkManager Dapr subscription fix

The WorkManager now connects DaprPublishSubscribeClient to its own Dapr sidecar port instead of the API Gateway's. Consumer groups are correctly named workmanager rather than apigateway.