Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Deploy With Helm

Use the iron-control Helm chart to run the self-hosted control plane on Kubernetes. The chart creates separate web and background job Deployments, a web Service, an optional Ingress, and a pre-install or pre-upgrade migration Job.

The chart lives in the iron-control repository under charts/iron-control.

Architecture

The chart creates three workloads:

WorkloadResourcePurpose
webDeploymentRuns the Rails web server through Thruster and serves the console and API.
jobsDeploymentRuns Solid Queue workers and the recurring job scheduler.
migrateHelm hook JobRuns rails db:prepare before install and upgrade.

PostgreSQL is the only external dependency. The app uses Solid Queue, Solid Cache, and Solid Cable, so Redis is not required.

Prerequisites

  • A Kubernetes cluster with kubectl and helm configured
  • A checked-out copy of the iron-control repository
  • The iron-control container image in a registry your cluster can pull from
  • PostgreSQL 14 or newer reachable from the cluster
  • A Kubernetes Secret with the Rails, database, and encryption secrets

The app expects four databases owned by the iron_control role:

  • iron_control_production
  • iron_control_production_cache
  • iron_control_production_queue
  • iron_control_production_cable

The migration Job can create these databases if the iron_control role has CREATEDB.

Create The Secret

For production, create the Secret yourself or with an external-secrets operator. The chart reads these keys from secrets.existingSecret:

KeyPurpose
RAILS_MASTER_KEYRails credentials encryption.
IRON_CONTROL_DATABASE_PASSWORDPassword for the iron_control Postgres role.
IRON_CONTROL_AR_ENCRYPTION_PRIMARY_KEYActiveRecord encryption primary key.
IRON_CONTROL_AR_ENCRYPTION_DETERMINISTIC_KEYActiveRecord encryption deterministic key.
IRON_CONTROL_AR_ENCRYPTION_KEY_DERIVATION_SALTActiveRecord encryption key derivation salt.

Optional first-boot keys can be added to the same Secret:

KeyPurpose
IRON_CONTROL_INITIAL_USER_EMAILEmail for the initial user.
IRON_CONTROL_INITIAL_USER_PASSWORDPassword for the initial user. Must be at least 12 characters.
IRON_CONTROL_INITIAL_API_KEYOptional plaintext API key. Must match the iak_ format.

Example:

kubectl create namespace iron-control
 
kubectl -n iron-control create secret generic iron-control-secrets \
  --from-literal=RAILS_MASTER_KEY="$RAILS_MASTER_KEY" \
  --from-literal=IRON_CONTROL_DATABASE_PASSWORD="$IRON_CONTROL_DATABASE_PASSWORD" \
  --from-literal=IRON_CONTROL_AR_ENCRYPTION_PRIMARY_KEY="$IRON_CONTROL_AR_ENCRYPTION_PRIMARY_KEY" \
  --from-literal=IRON_CONTROL_AR_ENCRYPTION_DETERMINISTIC_KEY="$IRON_CONTROL_AR_ENCRYPTION_DETERMINISTIC_KEY" \
  --from-literal=IRON_CONTROL_AR_ENCRYPTION_KEY_DERIVATION_SALT="$IRON_CONTROL_AR_ENCRYPTION_KEY_DERIVATION_SALT" \
  --from-literal=IRON_CONTROL_INITIAL_USER_EMAIL="admin@example.com" \
  --from-literal=IRON_CONTROL_INITIAL_USER_PASSWORD="$IRON_CONTROL_INITIAL_USER_PASSWORD"

Generate ActiveRecord encryption keys with:

bin/rails db:encryption:init

Keep these values stable. Rotating them makes existing encrypted secret data unreadable unless you perform a planned Rails key rotation.

Install The Chart

Create a values.yaml file:

image:
  repository: ghcr.io/ironsh/iron-control
  tag: v1.2.3
 
database:
  host: postgres.example.internal
  port: 5432
 
secrets:
  existingSecret: iron-control-secrets
 
web:
  replicas: 2
 
jobs:
  replicas: 1
 
ingress:
  enabled: true
  className: nginx
  hosts:
    - host: control.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: iron-control-tls
      hosts:
        - control.example.com

Install from the repository checkout:

helm install iron-control ./charts/iron-control \
  --namespace iron-control \
  --values values.yaml

For upgrades:

helm upgrade iron-control ./charts/iron-control \
  --namespace iron-control \
  --values values.yaml

The migration Job runs before each install and upgrade when migrations.enabled is true.

Inline Secrets

If secrets.existingSecret is empty, the chart creates a Secret from secrets.values.*.

secrets:
  values:
    railsMasterKey: ""
    databasePassword: ""
    arEncryptionPrimaryKey: ""
    arEncryptionDeterministicKey: ""
    arEncryptionKeyDerivationSalt: ""
 
bootstrap:
  initialUserEmail: admin@example.com
  initialUserPassword: "change-this-long-password"
  initialApiKey: ""

Use this only for evaluation. Inline values are stored in Helm release history, and the chart-managed Secret is hook-annotated so Helm does not remove it on uninstall.

Configure Web And Jobs

The web Deployment serves /up for startup, readiness, and liveness probes. The jobs Deployment runs bin/jobs with a Recreate strategy so a rollout does not briefly double scheduler capacity.

config:
  logLevel: info
  webConcurrency: 1
  railsMaxThreads: 3
  jobConcurrency: 1
 
web:
  replicas: 2
  resources:
    requests:
      cpu: 250m
      memory: 512Mi
    limits:
      memory: 1Gi
 
jobs:
  replicas: 1
  resources:
    requests:
      cpu: 250m
      memory: 512Mi
    limits:
      memory: 1Gi

Do not set IRON_CONTROL_SOLID_QUEUE_IN_PUMA with extraEnv. The chart runs jobs in a dedicated Deployment.

Expose The Console

Use an Ingress for normal operation:

ingress:
  enabled: true
  className: nginx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt
  hosts:
    - host: control.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: iron-control-tls
      hosts:
        - control.example.com

For local access without Ingress:

kubectl -n iron-control port-forward svc/iron-control-web 8080:80

Then open http://localhost:8080.

Verify The Release

Render the chart before installing:

helm lint ./charts/iron-control
 
helm template iron-control ./charts/iron-control \
  --namespace iron-control \
  --set image.repository=ghcr.io/ironsh/iron-control \
  --set database.host=postgres.example.internal \
  --set secrets.existingSecret=iron-control-secrets

After installing:

kubectl -n iron-control get jobs,pods,svc,ingress
 
kubectl -n iron-control logs job/iron-control-migrate
 
kubectl -n iron-control rollout status deploy/iron-control-web
kubectl -n iron-control rollout status deploy/iron-control-jobs

If the migration Job fails, inspect its logs before retrying. Failed hook Jobs are kept for debugging and replaced on the next Helm attempt.

Important Values

ValueDefaultDescription
image.repository""Required image repository.
image.tagchart app versionContainer image tag.
database.host""Required Postgres hostname.
database.port5432Postgres port.
secrets.existingSecret""Existing Secret with required keys. Recommended for production.
secrets.values.*""Inline secrets used only when existingSecret is unset.
bootstrap.*""Optional first-boot user and API key for chart-managed Secrets.
config.logLevelinfoRails log level.
config.webConcurrency1Puma worker process count.
config.railsMaxThreads3Puma threads and ActiveRecord pool size.
config.jobConcurrency1Solid Queue worker process count.
web.replicas2Web pod count.
jobs.replicas1Jobs pod count. More than one is safe but rarely needed.
migrations.enabledtrueRun rails db:prepare as a Helm hook Job.
service.typeClusterIPWeb Service type.
service.port80Web Service port.
ingress.enabledfalseCreate an Ingress.
extraEnv and extraEnvFrom[]Environment additions for all workloads.
web.extraEnv and jobs.extraEnv[]Environment additions for one workload.
serviceAccount.*createdServiceAccount settings for web and jobs pods.
podSecurityContextnon-rootPod security context.
containerSecurityContextno capabilitiesContainer security context.