SPENDLE / DOCUMENTATION / DEPLOYMENT

Your environment. Ready for Spendle.

Deploy the backend and web workspace with the charts supplied by MTD. This guide follows the chart configuration in the Spendle repository.

What you deploy

The delivered application includes a Ktor backend chart at server/chart and a Next.js workspace chart at webApp/chart. The charts deploy application workloads; PostgreSQL, Keycloak, Google Cloud Storage, DNS and TLS must be provided separately.

The backend normally runs as a per-client instance with CONTROL_PLANE_ENABLED: "false". Keep that setting for client deployments. The centrally hosted tenant registry is a separate deployment. The web workspace signs mobile provisioning QR codes from its runtime configuration and also connects to the backend for authenticated workspace operations.

Prepare infrastructure and access

  • A supported Kubernetes cluster and Helm 3 or later, with kubectl access to the target namespace.
  • A reachable PostgreSQL database and a dedicated database account.
  • A Keycloak realm with web, mobile and service clients, appropriate roles and registered redirect URIs. Agree the realm/client configuration with MTD before deployment.
  • A Google Cloud Storage bucket for uploaded documents and credentials with appropriate access.
  • Access to the delivered container image versions. The chart repositories are ghcr.io/mtdtechnology-net/spendle-bk and ghcr.io/mtdtechnology-net/spendle-web; private packages require a pull secret.
  • Public or organization-reachable backend and web URLs with TLS. Mobile devices must reach both the backend and identity provider.
  • The provisioning signing key supplied or approved by MTD, matching the public key trusted by the distributed mobile applications.

Create runtime secrets

Create the namespace before installing secret resources:

kubectl create namespace spendle

Use your organization’s secret manager or a controlled Kubernetes secret process. Do not put production credentials in committed values files or shell history.

SecretRequired keys
spendle-bk-runtime-envDB_PASSWORD, KEYCLOAK_CLIENT_SECRET, KEYCLOAK_ADMIN_CLIENT_SECRET, SPENDLE_KEYCLOAK_CLIENT_SECRET, FX_API_TOKEN
spendle-web-runtime-envNEXTAUTH_SECRET, KEYCLOAK_SECRET, PROVISIONING_SIGNING_PRIVATE_KEY
spendle-bk-gcp-credentialslatest: the GCS service-account JSON credential file
ghcr-pull-secretRegistry authentication in Kubernetes image-pull-secret format

The provisioning private key is a base64-encoded PKCS#8 P-256 key. An arbitrary replacement key will not be trusted by existing mobile apps. Coordinate key issuance and rotation with MTD. The backend needs a signing key only in the separate control-plane deployment.

The GCS mount defaults to /secrets/gcp/credentials.json. TLS secrets referenced by ingress must also exist, or be provisioned by your certificate management process.

Configure the backend chart

Save an environment values file such as backend-values.yaml. Replace every example host, bucket and version with your approved values. These examples describe the application’s chart configuration; your identity provider may require additional settings.

image:
  tag: "REPLACE_WITH_DELIVERED_VERSION"
imagePullSecrets:
  - name: ghcr-pull-secret
env:
  DB_HOST: "postgresql.example.internal"
  DB_PORT: "5432"
  DB_NAME: "spendle"
  DB_USER: "spendle"
  GCS_BUCKET_NAME: "YOUR_DOCUMENT_BUCKET"
  SPENDLE_SERVICE_CLIENT_ID: "spendle-client"
  CONTROL_PLANE_ENABLED: "false"
  KEYCLOAK_ISSUER: "https://identity.example.com/realms/spendle"
  KEYCLOAK_AUTHORIZE_URL: "https://identity.example.com/realms/spendle/protocol/openid-connect/auth"
  KEYCLOAK_TOKEN_URL: "https://identity.example.com/realms/spendle/protocol/openid-connect/token"
  KEYCLOAK_LOGOUT_URL: "https://identity.example.com/realms/spendle/protocol/openid-connect/logout"
  KEYCLOAK_JWKS_URL: "https://identity.example.com/realms/spendle/protocol/openid-connect/certs"
  KEYCLOAK_ADMIN_BASE_URL: "https://identity.example.com/admin/realms/spendle"
  KEYCLOAK_CALLBACK_URL: "https://api.example.com/callback"
secrets:
  create: false
  name: spendle-bk-runtime-env
gcpCredentials:
  enabled: true
  secretName: spendle-bk-gcp-credentials
ingress:
  enabled: true
  className: nginx
  hosts:
    - host: api.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: spendle-api-tls
      hosts: [api.example.com]

Register the backend’s /callback URL with the appropriate Keycloak client. Keep all realm URLs consistent with the mobile and web configuration. The default backend service port is 8080.

Configure the web workspace

Save web-values.yaml. Register https://expenses.example.com/api/auth/callback/keycloak as the web client’s redirect URI, using your actual workspace host. The default web service port is 3000.

image:
  tag: "REPLACE_WITH_DELIVERED_VERSION"
imagePullSecrets:
  - name: ghcr-pull-secret
env:
  NEXTAUTH_URL: "https://expenses.example.com"
  KEYCLOAK_ID: "spendle-ops-client"
  KEYCLOAK_ISSUER: "https://identity.example.com/realms/spendle"
  BACKEND_BASE_URL: "https://api.example.com"
  PROVISIONING_COMPANY_NAME: "Your organization"
  PROVISIONING_BACKEND_BASE_URL: "https://api.example.com"
  PROVISIONING_KEYCLOAK_ISSUER: "https://identity.example.com/realms/spendle"
  PROVISIONING_KEYCLOAK_CLIENT_ID: "spendle-client"
  PROVISIONING_GOOGLE_IDP_HINT: ""
secrets:
  create: false
  name: spendle-web-runtime-env
ingress:
  enabled: true
  className: nginx
  annotations:
    nginx.ingress.kubernetes.io/proxy-buffer-size: "16k"
    nginx.ingress.kubernetes.io/proxy-buffers-number: "4"
  hosts:
    - host: expenses.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: spendle-web-tls
      hosts: [expenses.example.com]

BACKEND_BASE_URL controls authenticated web requests and falls back to PROVISIONING_BACKEND_BASE_URL if omitted. The provisioning URL must be reachable by mobile devices. Preserve the ingress proxy-buffer annotations where nginx is used to accommodate authentication response headers.

Validate and install

Run from the root of the delivered Spendle distribution. First lint and render both charts to inspect the resources, then install them with pinned image tags:

helm lint ./server/chart -f backend-values.yaml
helm lint ./webApp/chart -f web-values.yaml
helm template spendle-bk ./server/chart -n spendle -f backend-values.yaml
helm template spendle-web ./webApp/chart -n spendle -f web-values.yaml

helm upgrade --install spendle-bk ./server/chart \
  -n spendle -f backend-values.yaml --wait --timeout 10m
helm upgrade --install spendle-web ./webApp/chart \
  -n spendle -f web-values.yaml --wait --timeout 10m

Do not render secret values into shared logs. Client values files should reference existing secrets with secrets.create: false.

Check the deployment

kubectl get pods,services,ingress -n spendle
kubectl get deployments -n spendle
kubectl get events -n spendle --sort-by=.metadata.creationTimestamp
  1. Check pod readiness and image-pull status. Investigate failures with sanitized deployment logs.
  2. Verify TLS and DNS on the backend, workspace and identity provider.
  3. Sign into the web workspace with a test account and verify role-appropriate access.
  4. Open the organization provisioning page, scan its QR with a mobile device and sign in.
  5. Create a test request or report, attach a document and complete a review decision with a second authorized account.
  6. Confirm storage permissions, audit visibility and your database backup procedure.

The backend currently uses TCP health probes on its application port; it does not expose a dedicated /health endpoint. TCP readiness alone does not prove successful database or identity-provider access. The web chart uses / and /api/auth/session probes.

Operate, upgrade and recover

Pin tested image versions, manage secrets outside source control and configure resource limits, monitoring and backups for your workload. The charts offer optional horizontal autoscaling; database and storage capacity are managed separately.

Before upgrades, back up PostgreSQL and confirm any migration instructions in the delivered release notes. Record your current chart values and image versions. Deploy and validate in a test environment before production.

helm history spendle-bk -n spendle
helm history spendle-web -n spendle
# If approved by your release process:
helm rollback spendle-bk PREVIOUS_REVISION -n spendle --wait
helm rollback spendle-web PREVIOUS_REVISION -n spendle --wait

A Helm rollback changes Kubernetes resources; it does not reverse database changes. Confirm schema compatibility and recovery requirements before rolling back. For provisioning changes or key rotation, coordinate compatibility with installed mobile apps.

Read support guidance for what to include when contacting MTD.