BasekickLabs

Kubernetes Installation

Deploy Arc on Kubernetes with the Helm chart: configure storage backends and credentials, size persistent volumes, set resource limits, and expose the service.

Deploy Arc on Kubernetes using Helm for production-grade analytical data management.

Prerequisites

  • Kubernetes 1.24+
  • Helm 3.0+
  • kubectl configured to access your cluster
  • Persistent storage (for local storage backend)

Quick start

# Install Arc
LATEST=$(curl -s https://api.github.com/repos/basekick-labs/arc/releases/latest | grep tag_name | cut -d '"' -f 4 | sed 's/v//')
helm install arc https://github.com/basekick-labs/arc/releases/download/v${LATEST}/arc-${LATEST}.tgz

# Port forward to access locally
kubectl port-forward svc/arc 8000:8000

# Verify installation
curl http://localhost:8000/health

Get your admin token

# Get the pod name
kubectl get pods -l app.kubernetes.io/name=arc

# View logs to find admin token
kubectl logs -l app.kubernetes.io/name=arc | grep -i "admin"

You should see:

======================================================================
  FIRST RUN - INITIAL ADMIN TOKEN GENERATED
======================================================================
  Admin API token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
======================================================================

Save This Token

Copy this token immediately - you won't see it again!

Set the admin token yourself

To skip the log lookup, give the chart the token to create on first run. Either let the chart create the Secret from a value:

# values.yaml
auth:
  bootstrapToken:
    value: "<at least 32 characters>"

or point it at a Secret you created, which must hold the key bootstrap-token:

kubectl create secret generic arc-admin-token \
  --from-literal=bootstrap-token="$(openssl rand -base64 32)"
# values.yaml
auth:
  bootstrapToken:
    existingSecret: arc-admin-token

The chart passes the token to Arc as ARC_AUTH_BOOTSTRAP_TOKEN. Arc uses it only on first run, when no token exists yet; after that it is ignored, so changing it on a later upgrade does not change the admin token. The chart refuses a value shorter than 32 characters. Arc would reject it and start with no admin token.

Prefer existingSecret in production: a value is stored in the Helm release along with the rest of your values.

Arc Enterprise cluster mode (v26.06.1+)

In a multi-pod Arc Enterprise cluster, only one pod prints the banner — the Raft leader that wins the bootstrap election. Other pods log INFO Deferring initial token bootstrap until cluster Raft proposer is wired during startup and then INFO Cluster auth state replication enabled — token writes now propagate via Raft once the leader is elected. The non-leader pods silently no-op the bootstrap (they get an "already exists" response from the leader's FSM) and converge on the leader's token via Raft. The same command with -l app.kubernetes.io/name=arc-enterprise still works — it just returns the single banner from whichever pod won the election. See Cluster auth replication for the full semantics, including token-propagation behaviour and the divergence-detection error log.

Installation methods

LATEST=$(curl -s https://api.github.com/repos/basekick-labs/arc/releases/latest | grep tag_name | cut -d '"' -f 4 | sed 's/v//')
helm install arc https://github.com/basekick-labs/arc/releases/download/v${LATEST}/arc-${LATEST}.tgz

Storage backends

The OSS chart selects the backend with arc.storageBackend (local, s3, or minio). The chart auto-sets ARC_STORAGE_BACKEND and ARC_STORAGE_LOCAL_PATH; everything else (bucket, region, keys, endpoint) is passed through as ARC_STORAGE_* environment variables via the free-form arc.env[] list.

Local disk on a Persistent Volume - the default. The chart provisions a PVC mounted at /app/data/arc.

# values.yaml
arc:
  storageBackend: local

persistence:
  enabled: true
  size: 100Gi
  accessMode: ReadWriteOnce
  storageClass: ""        # default storage class
helm install arc ./arc -f values.yaml

Configuration profiles

The OSS chart is a single Deployment. Arc's own settings (auth, WAL, log level, ingest buffers, etc.) are passed as environment variables through arc.env[], or as a whole arc.toml through arc.config (below).

Minimal resources for development/testing:

# values-dev.yaml
replicaCount: 1

resources:
  requests:
    memory: "512Mi"
    cpu: "250m"
  limits:
    memory: "2Gi"
    cpu: "1"

arc:
  storageBackend: local
  env:
    - name: ARC_AUTH_ENABLED
      value: "false"
    - name: ARC_LOG_LEVEL
      value: "debug"

persistence:
  enabled: true
  size: 10Gi
helm install arc ./arc -f values-dev.yaml

Supplying a whole arc.toml

arc.config mounts an arc.toml from a ConfigMap at /app/arc.toml, in place of the one in the image:

# values.yaml
arc:
  config:
    enabled: true
    contents: |
      [log]
      level = "info"
      format = "json"

Or keep the file next to your values and load it with --set-file arc.config.contents=arc.toml.

Environment variables override the file. The chart always sets ARC_STORAGE_BACKEND, ARC_STORAGE_LOCAL_PATH, ARC_WAL_ENABLED and ARC_WAL_DIRECTORY, so set those through arc.storageBackend and arc.wal, not in the file. Anything in arc.env[] also wins over the file.

Changing arc.config.contents on helm upgrade restarts the pod, because a running pod never sees a change to a file mounted this way.

Chart from Arc 27.01.1

Earlier OSS charts did not include the ConfigMap template: with arc.config.enabled: true the pod stayed in ContainerCreating with MountVolume.SetUp failed for volume "config". Upgrade the chart, or use arc.env[] instead. auth.bootstrapToken is also new in this chart.

Sizing the data volume when the WAL is on

With an object-storage backend (storageBackend: s3), the pod's PersistentVolume holds only the WAL and local cache — but that does not make it small. The WAL absorbs everything between ingest and flush, so size it against your peak ingest rate × worst-case flush lag, with generous headroom. The default persistence.size: 10Gi is a development default: at a bulk-load rate of 100 MB/s it fills in under two minutes.

Before v27.01.1 a full WAL volume was a bad failure. Writes kept being accepted while their WAL entries could not be written, and the writer could not boot again on a full disk. Since v27.01.1 Arc refuses writes with a retryable 503 once the volume runs low (arc.wal.diskMinFreeMB), and starts on a full volume by releasing a reserve file. See When the WAL volume runs low. Size the volume for your ingest rate anyway: while writes are refused, clients must retry.

With persistence.enabled: false the data lives in an emptyDir, which reports the node's free space rather than its size limit. The chart then caps the WAL directory at 80% of persistence.emptyDirSizeLimitMB, or at 1024 MB when no limit is set. Local Parquet files, arc.db and temp files share that emptyDir, so leave room for them too.

Helm values reference

Core settings

# Number of pod replicas (single Deployment).
replicaCount: 1

# Container image (tag defaults to the chart appVersion).
image:
  repository: ghcr.io/basekick-labs/arc
  tag: ""                # set "26.06.2" for full IRSA query-read support
  pullPolicy: IfNotPresent

imagePullSecrets: []

# Service configuration
service:
  type: ClusterIP
  port: 8000

Resources

resources: {}            # empty by default; set requests/limits as needed
  # requests:
  #   memory: "2Gi"
  #   cpu: "1"
  # limits:
  #   memory: "8Gi"
  #   cpu: "4"

Storage

arc:
  # local | s3 | minio. The chart sets ARC_STORAGE_BACKEND from this and
  # ARC_STORAGE_LOCAL_PATH automatically.
  storageBackend: local

  # Write-ahead log. On by default in the chart; the Arc binary itself
  # defaults wal.enabled to false for backwards compatibility, so the chart
  # overrides it. Without a WAL a crash loses every record buffered since the
  # last flush.
  wal:
    enabled: true
    directory: /app/data/wal
    # Refuse writes with 503 + Retry-After when the volume runs low (v27.01.1+).
    diskPressureEnabled: true
    diskMinFreeMB: 0        # 0 = max(512, 2 x wal.max_size_mb), at most a fifth of the volume
    maxDirectoryMB: 0       # WAL directory cap; 0 = none (set automatically with persistence disabled)

  # Free-form env vars passed straight to the Arc container. Use ARC_STORAGE_*
  # for bucket/region/keys/endpoint, plus any other ARC_* settings.
  env: []
    # - name: ARC_STORAGE_S3_BUCKET
    #   value: arc-production
    # - name: ARC_STORAGE_S3_REGION
    #   value: us-east-1
    # - name: ARC_STORAGE_S3_ENDPOINT
    #   value: https://s3.us-east-1.amazonaws.com
    # - name: ARC_STORAGE_S3_ACCESS_KEY   # omit for IRSA
    #   value: ""
    # - name: ARC_STORAGE_S3_SECRET_KEY   # omit for IRSA
    #   value: ""
    # - name: ARC_STORAGE_S3_USE_SSL
    #   value: "true"
    # - name: ARC_STORAGE_S3_PATH_STYLE
    #   value: "false"

  # Whole arc.toml, mounted at /app/arc.toml. Environment variables win
  # over it. A change rolls the pod.
  config:
    enabled: false
    contents: ""

# Initial admin token, used on first run only. value: at least 32 characters.
# existingSecret: a Secret with the key "bootstrap-token".
auth:
  bootstrapToken:
    existingSecret: ""
    value: ""

# PVC used when storageBackend is local (mounted at /app/data/arc).
persistence:
  enabled: true
  accessMode: ReadWriteOnce
  size: 10Gi
  storageClass: ""
  # existingClaim: ""

Ingress

ingress:
  enabled: false
  className: ""
  annotations: {}
  hosts:
    - host: arc.local
      paths:
        - path: /
          pathType: Prefix
  tls: []

Service account

serviceAccount:
  create: true
  automount: true
  name: ""
  annotations: {}        # eks.amazonaws.com/role-arn for IRSA on EKS

Clustering & HA are Enterprise features

The OSS chart runs Arc as a single Deployment with no writer/reader/compactor roles, Raft clustering, or multi-writer failover. For high availability, multi-writer ingest, and peer replication see the Arc Enterprise Kubernetes guide.

Operations

View logs

# Follow logs
kubectl logs -l app.kubernetes.io/name=arc -f

# Last 100 lines
kubectl logs -l app.kubernetes.io/name=arc --tail=100

# Logs from last hour
kubectl logs -l app.kubernetes.io/name=arc --since=1h

Check status

# Pod status
kubectl get pods -l app.kubernetes.io/name=arc

# Describe pod
kubectl describe pod -l app.kubernetes.io/name=arc

# Check events
kubectl get events --field-selector involvedObject.name=arc-0

Scale (restart)

# Restart pod
kubectl rollout restart deployment arc

# Or delete pod (will be recreated)
kubectl delete pod -l app.kubernetes.io/name=arc

Port forward

kubectl port-forward svc/arc 8000:8000

Access shell

kubectl exec -it $(kubectl get pod -l app.kubernetes.io/name=arc -o jsonpath='{.items[0].metadata.name}') -- /bin/sh

Upgrade

# Upgrade to new version
LATEST=$(curl -s https://api.github.com/repos/basekick-labs/arc/releases/latest | grep tag_name | cut -d '"' -f 4 | sed 's/v//')
helm upgrade arc https://github.com/basekick-labs/arc/releases/download/v${LATEST}/arc-${LATEST}.tgz

# With custom values
helm upgrade arc ./arc -f values-prod.yaml

Uninstall

# Uninstall Arc
helm uninstall arc

# Delete PVCs (optional - removes all data!)
kubectl delete pvc -l app.kubernetes.io/name=arc

# Delete namespace (if dedicated)
kubectl delete namespace arc

Monitoring

Prometheus metrics

Arc exposes Prometheus metrics at /metrics:

# ServiceMonitor for Prometheus Operator
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: arc
spec:
  selector:
    matchLabels:
      app: arc
  endpoints:
    - port: http
      path: /metrics
      interval: 30s

Readiness/liveness probes

livenessProbe:
  httpGet:
    path: /health
    port: 8000
  initialDelaySeconds: 10
  periodSeconds: 30

readinessProbe:
  httpGet:
    path: /ready
    port: 8000
  initialDelaySeconds: 5
  periodSeconds: 10

As of 26.09.1, /health includes a storage field with per-tier credential state (ok / degraded / expired / fallback / unknown), computed from Arc's credential refresher — no S3/Azure probing from the probe path. Alert on storage.*.state != "ok" or on expires_at approaching. For reader pools, setting server.storage_credentials_fail_ready = true makes /ready return 503 while any tier's credentials are expired, so Kubernetes recycles the pod (a restart re-resolves credentials). Leave it off for writers — ingest keeps working through credential expiry, and Arc warns at startup if it is enabled on a cluster writer.

Troubleshooting

Pod won't start

# Check pod status
kubectl describe pod -l app.kubernetes.io/name=arc

# Check events
kubectl get events --sort-by='.lastTimestamp'

# Common issues:
# - ImagePullBackOff: Check image name/tag
# - Pending: Check PVC status, node resources
# - CrashLoopBackOff: Check logs

Storage issues

# Check PVC status
kubectl get pvc -l app.kubernetes.io/name=arc

# Check PV
kubectl get pv

# Describe PVC for errors
kubectl describe pvc -l app.kubernetes.io/name=arc

Connection issues

# Check service
kubectl get svc arc

# Test from within cluster
kubectl run curl --image=curlimages/curl -it --rm -- curl http://arc:8000/health

Memory issues

# Check resource usage
kubectl top pod -l app.kubernetes.io/name=arc

# Increase limits in values.yaml
resources:
  limits:
    memory: "16Gi"

Next steps

On this page