Run the leader on Kubernetes

This deployment runs the leader as one persistent replica. Smeltr does not provide a multi-replica or active/standby leader in this release. To run workers in the cluster, see Enroll workers on Kubernetes; to upgrade, see Upgrade Smeltr.

Smeltr on KubernetesAn administrator reaches the ingress over HTTPS. The ingress forwards through a private ClusterIP Service to one leader pod. Workers fetch main and report status through the ingress and connect directly to sources and destinations. The leader and each worker have persistent storage.Fetch mainReport statusPrivate HTTPAdministratorBrowser · HTTPSIngressHTTPS entry pointLeaderOne pod · private ServiceSourcesInput recordsWorkersOne process per podDestinationsKept recordsWorker storageOne volume per workerLeader storageData + certificatesSeparate secret-key volumeSmeltr on KubernetesAn administrator reaches the ingress over HTTPS. The ingress forwards through a private ClusterIP Service to one leader pod. Workers fetch main and report status through the ingress and connect directly to sources and destinations. The leader and each worker have persistent storage.AdministratorBrowser · HTTPSIngressHTTPS entry pointLeaderOne pod · private ServiceLeader storageData + certificatesSeparate secret-key volumeSourcesInput recordsWorkersOne process per podWorker storageOne volume per workerDestinationsKept records
Dashed arrows show workers fetching main and reporting status through the ingress HTTPS address. The ingress forwards to the leader through a private ClusterIP Service. Dotted lines connect processes to persistent volumes: keep leader data and its sealing key on separate volumes, and give each worker its own state volume. Back up the volumes. Sources and destinations may be inside or outside the cluster.

Requirements and image

Before deploying, provide:

  • A Kubernetes cluster with a default or explicitly selected StorageClass that supports ReadWriteOnce persistent volumes.
  • An ingress controller, DNS name and a TLS certificate that browsers and workers trust.
  • Nodes that can pull from ghcr.io, or a registry your organization mirrors the image into.

Pull the release's image, verify its signature, and name it by that digest in the manifests: a tag can move to a newer image, a digest cannot. Keep the digest and the release notes with the deployment. If you mirror the image, replace the whole image reference, registry and digest, in every manifest, the workers' included.

Deploy the leader

Save the following as smeltr-leader.yaml. Replace the public hostname, image digest and storage requests for your environment. TLS terminates at the ingress, so Smeltr serves HTTP only inside the cluster.

The port default is 443, which is privileged, and the pod runs as user 1000 -- so leader.http.port names an unprivileged port instead. Set it in the file rather than the environment: the file only backfills variables that are unset, so an environment variable wins over it. fsGroup matches the image's group, without which the process cannot write a freshly provisioned volume.

The container's own securityContext meets the Kubernetes "restricted" Pod Security Standard: no privilege escalation, no capabilities, the runtime's default seccomp profile, and a read-only root filesystem. Smeltr writes only to its volumes, so nothing else needs to be writable.

The leader makes its data directory and the sealing key's directory private to itself, and only a directory's owner can do that. A volume's root belongs to root whatever fsGroup says, so data_dir and key name directories inside their volumes, which the leader creates on first start.

apiVersion: v1
kind: ConfigMap
metadata:
  name: smeltr-leader
data:
  smeltr.toml: |
    [leader]
    enabled = true

    [leader.http]
    bind = "0.0.0.0"
    port = 4000
    public_origin = "https://smeltr.example.com"

    [leader.tls]
    enabled = false

    [leader.storage]
    data_dir = "/var/lib/smeltr/data"

    [leader.secrets]
    key = "/var/lib/smeltr-secrets/key/sealing.key"
---
apiVersion: v1
kind: Service
metadata:
  name: smeltr-leader
spec:
  type: ClusterIP
  selector:
    app: smeltr-leader
  ports:
    - name: http
      port: 4000
      targetPort: http
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: smeltr-leader
spec:
  serviceName: smeltr-leader
  replicas: 1
  selector:
    matchLabels:
      app: smeltr-leader
  template:
    metadata:
      labels:
        app: smeltr-leader
    spec:
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        fsGroup: 1000
      containers:
        - name: smeltr
          image: ghcr.io/smeltr-sh/smeltr@sha256:REPLACE_WITH_DIGEST
          imagePullPolicy: IfNotPresent
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop: [ALL]
            seccompProfile:
              type: RuntimeDefault
          env:
            - name: SMELTR_CONFIG_FILE
              value: /config/smeltr.toml
          ports:
            - name: http
              containerPort: 4000
          volumeMounts:
            - name: config
              mountPath: /config
              readOnly: true
            - name: leader-data
              mountPath: /var/lib/smeltr
            - name: leader-secrets
              mountPath: /var/lib/smeltr-secrets
      volumes:
        - name: config
          configMap:
            name: smeltr-leader
  volumeClaimTemplates:
    - metadata:
        name: leader-data
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 10Gi
    - metadata:
        name: leader-secrets
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 1Gi

Apply it in the intended namespace, wait for smeltr-leader-0, and inspect its console for startup errors:

kubectl apply -f smeltr-leader.yaml
kubectl rollout status statefulset/smeltr-leader
kubectl logs smeltr-leader-0

A new leader prints the initial administrator username and one-time password to stderr. Treat the pod log as sensitive until you change that password. The data and sealing-key claims are deliberately separate; back them up as one matched recovery set.

Expose the leader

Create an Ingress using the same hostname as leader.http.public_origin. Controller annotations and certificate provisioning are cluster-specific:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: smeltr
spec:
  tls:
    - hosts:
        - smeltr.example.com
      secretName: smeltr-ingress-tls
  rules:
    - host: smeltr.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: smeltr-leader
                port:
                  name: http

Keep the Service as ClusterIP; only the ingress should be externally reachable. Configure the ingress to preserve the browser Origin header. Open the configured public origin URL, change the initial password, and create named administrator accounts. Because TLS terminates before Smeltr, workers use platform trust for the ingress certificate.