CKAN
Deploy CKAN on Kubernetes — the world’s leading open-source data management system for publishing, sharing, and discovering datasets. Powers government open-data portals and data hubs worldwide.
CKAN uses ckan.siteUrl to generate absolute URLs for datasets, resources, and API responses. Setting it to the wrong
value (such as the default http://localhost:5000) causes all links to be incorrect and OAuth callbacks to fail.
Always set it to the full public URL before deployment.
The optional DataStore extension requires a separate datastore database with a dedicated read-only role. The bundled
PostgreSQL topology supports the CKAN catalog only. Create the DataStore database and roles externally with the
upstream permission script before enabling datastore and datapusher.
Key Features
- Configurable uWSGI application — the real listener and probes follow
ckan.containerPort - Optional DataPusher — CSV/Excel loading when an external DataStore is prepared
- CKAN-specific Solr —
ckan/ckan-solrStatefulSet with bundled search schema - PostgreSQL and Redis contracts — bundled or structured external connections with Secret-backed passwords
- Three secrets — sysadmin password, Beaker session secret, JWT secret
- pg_dump backup — daily PostgreSQL S3 backup CronJob
- Secure defaults — dedicated ServiceAccount, no API token mount, hardened contexts, and NetworkPolicy
Installation
HTTPS repository:
helm repo add helmforge https://repo.helmforge.dev
helm repo update
helm install ckan helmforge/ckan -f values.yaml
OCI registry:
helm install ckan oci://ghcr.io/helmforgedev/helm/ckan -f values.yaml
Deployment Examples
# values.yaml — CKAN catalog with bundled PostgreSQL, Redis, and Solr
ckan:
containerPort: 5000
siteUrl: 'https://data.example.com' # required; wrong value breaks all links
siteTitle: 'My Open Data Portal'
sysadminName: admin
sysadminEmail: [email protected]
existingSecret: ckan-secrets
existingSecretPasswordKey: sysadmin-password
existingSecretSessionKey: session-secret
existingSecretJwtKey: jwt-secret
postgresql:
enabled: true
auth:
database: ckan
username: ckan
password: 'strong-db-password'
redis:
enabled: true
auth:
password: 'strong-redis-password'
solr:
enabled: true
persistence:
size: 10Gi
datapusher:
enabled: false # requires an external, pre-provisioned DataStore
persistence:
enabled: true
size: 50Gi # uploaded datasets and resources
ingress:
enabled: true
ingressClassName: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: data.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: ckan-tls
hosts:
- data.example.com# values.yaml — CKAN with external PostgreSQL (both ckan + datastore DBs) and Redis
# Both databases must be created in PostgreSQL before deploying:
# CREATE DATABASE ckan;
# CREATE DATABASE datastore;
# CREATE USER ckan WITH PASSWORD '...';
# CREATE USER datastore_ro WITH PASSWORD '...';
# GRANT ALL ON DATABASE ckan TO ckan;
# GRANT CONNECT ON DATABASE datastore TO ckan;
# GRANT CONNECT ON DATABASE datastore TO datastore_ro;
ckan:
siteUrl: 'https://data.example.com'
existingSecret: ckan-secrets
plugins: 'envvars image_view text_view datatables_view datastore datapusher'
extraEnv:
- name: PGSSLROOTCERT
value: /etc/ckan/postgresql-ca/ca.crt
postgresql:
enabled: false
redis:
enabled: false
database:
mode: external
external:
host: postgres.database.svc.cluster.local
port: 5432
ckanDatabase: ckan
datastoreDatabase: datastore
username: ckan
datastoreReadUsername: datastore_ro
existingSecret: ckan-db-credentials
existingSecretPasswordKey: password
existingSecretDatastoreReadPasswordKey: datastore-read-password
sslMode: verify-full
redisConfig:
mode: external
external:
host: redis.cache.svc.cluster.local
port: 6379
database: 0
tls: true
sslCertReqs: required
existingSecret: ckan-redis-credentials
existingSecretPasswordKey: redis-password
datapusher:
enabled: true
solr:
enabled: true # always use the bundled CKAN-specific Solr image
extraVolumes:
- name: postgresql-ca
secret:
secretName: postgresql-ca
extraVolumeMounts:
- name: postgresql-ca
mountPath: /etc/ckan/postgresql-ca
readOnly: true
ingress:
enabled: true
ingressClassName: traefik
hosts:
- host: data.example.com
paths:
- path: /
pathType: Prefix# values.yaml — CKAN listener tuning and image-provided plugins
ckan:
siteUrl: 'https://data.example.com'
existingSecret: ckan-secrets
containerPort: 5500
uwsgiProcesses: 4
uwsgiHarakiri: 120
# Only enable plugins installed in the selected image.
plugins: 'envvars image_view text_view datatables_view'
postgresql:
enabled: true
auth:
password: 'strong-db-password'
redis:
enabled: true
auth:
password: 'strong-redis-password'
datapusher:
enabled: false# values.yaml — CKAN with daily pg_dump backup
# Note: backup covers the ckan database only; uploaded files in /var/lib/ckan (PVC)
# are NOT included. Back up the PVC separately using Velero or storage snapshots.
ckan:
siteUrl: 'https://data.example.com'
existingSecret: ckan-secrets
postgresql:
enabled: true
auth:
password: 'strong-db-password'
redis:
enabled: true
auth:
password: 'strong-redis-password'
backup:
enabled: true
schedule: '0 3 * * *'
s3:
endpoint: https://s3.amazonaws.com
bucket: ckan-backups
existingSecret: ckan-s3-credentials
ingress:
enabled: true
ingressClassName: traefik
hosts:
- host: data.example.com
paths:
- path: /
pathType: PrefixConfiguration Reference
Image
| Parameter | Type | Default | Description |
|---|---|---|---|
image.repository |
string | docker.io/ckan/ckan-base |
CKAN image. |
image.tag |
string | "2.12.0" |
Image tag. |
CKAN Application
| Parameter | Type | Default | Description |
|---|---|---|---|
ckan.containerPort |
integer | 5000 |
Real uWSGI listener port. |
ckan.uwsgiProcesses |
integer | 2 |
uWSGI worker processes. |
ckan.uwsgiHarakiri |
integer | 60 |
Maximum request duration in seconds. |
ckan.siteUrl |
string | http://localhost:5000 |
Required. Full public URL (breaks all links if wrong). |
ckan.siteTitle |
string | CKAN |
Portal display name. |
ckan.sysadminName |
string | admin |
Sysadmin username. |
ckan.sysadminEmail |
string | [email protected] |
Sysadmin email. |
ckan.sysadminPassword |
string | "" |
Sysadmin password. Auto-generated if empty. |
ckan.existingSecret |
string | "" |
Existing secret with all three secrets. |
ckan.existingSecretPasswordKey |
string | sysadmin-password |
Key for sysadmin password. |
ckan.existingSecretSessionKey |
string | session-secret |
Key for Beaker session secret. |
ckan.existingSecretJwtKey |
string | jwt-secret |
Key for JWT secret. |
ckan.plugins |
string | envvars image_view text_view ... |
Space-separated list of active CKAN plugins. |
ckan.replicaCount |
integer | 1 |
CKAN web pod replicas. |
ckan.extraEnv |
array | [] |
Extra environment variables. |
DataPusher
| Parameter | Type | Default | Description |
|---|---|---|---|
datapusher.enabled |
boolean | true |
Deploy DataPusher; external DataStore is still required. |
datapusher.replicaCount |
integer | 1 |
DataPusher pod replicas. |
datapusher.port |
integer | 8800 |
DataPusher service port. |
Solr
The bundled Solr uses the ckan/ckan-solr image with a CKAN-specific schema. Connecting another application to this
Solr instance may overwrite the schema and break CKAN’s search indexing.
| Parameter | Type | Default | Description |
|---|---|---|---|
solr.enabled |
boolean | true |
Deploy the bundled CKAN-specific Solr StatefulSet. |
solr.port |
integer | 8983 |
Real Solr listener and Service port. |
solr.persistence.size |
string | 5Gi |
Solr PVC size. |
solr.externalUrl |
string | "" |
External Solr URL (when solr.enabled: false). |
Database
| Parameter | Type | Default | Description |
|---|---|---|---|
database.mode |
string | subchart |
Mode: subchart or external. |
database.external.host |
string | "" |
External PostgreSQL hostname. |
database.external.ckanDatabase |
string | ckan |
CKAN main database name. |
database.external.datastoreDatabase |
string | datastore |
DataStore extension database name. |
database.external.datastoreReadUsername |
string | datastore_ro |
Read-only user for the DataStore API. |
database.external.existingSecret |
string | "" |
Existing secret with database passwords. |
database.external.existingSecretPasswordKey |
string | password |
CKAN/DataStore write-role password key. |
database.external.existingSecretDatastoreReadPasswordKey |
string | datastore-read-password |
DataStore read-role password key. |
database.external.sslMode |
string | require |
libpq SSL mode; use verify-full with a trusted CA for certificate and hostname verification. |
postgresql.enabled |
boolean | true |
Deploy the bundled PostgreSQL subchart. |
postgresql.startupProbe.initialDelaySeconds |
integer | 30 |
Delay startup checks until PostgreSQL completes its temporary init server cycle. |
postgresql.readinessProbe.initialDelaySeconds |
integer | 30 |
Delay readiness checks until PostgreSQL completes its temporary init server cycle. |
postgresql.auth.password |
string | "" |
Password. Auto-generated if empty. |
Redis
| Parameter | Type | Default | Description |
|---|---|---|---|
redisConfig.mode |
string | subchart |
Mode: subchart or external. |
redisConfig.external.host |
string | "" |
External Redis hostname. |
redisConfig.external.port |
integer | 6379 |
External Redis port. |
redisConfig.external.database |
integer | 0 |
Redis logical database. |
redisConfig.external.tls |
boolean | true |
Use rediss://. |
redisConfig.external.sslCertReqs |
string | required |
TLS certificate verification mode. |
redisConfig.external.sslCaCerts |
string | "" |
Mounted CA bundle path. |
redisConfig.external.existingSecret |
string | "" |
Secret containing the password. |
redisConfig.external.existingSecretPasswordKey |
string | redis-password |
Password key in the Secret. |
redis.enabled |
boolean | true |
Deploy the bundled Redis subchart. |
redis.auth.password |
string | "" |
Password. Auto-generated if empty. |
Persistence and Service
| Parameter | Type | Default | Description |
|---|---|---|---|
persistence.enabled |
boolean | true |
Enable PVC for /var/lib/ckan (uploaded resources). |
persistence.size |
string | 10Gi |
PVC size. |
service.port |
integer | 80 |
Service port. |
ingress.enabled |
boolean | false |
Enable an Ingress resource. |
ingress.ingressClassName |
string | traefik |
Ingress class name. |
service.port is independent from the process listener. Change
ckan.containerPort to move uWSGI itself.
Network and identity
| Parameter | Type | Default | Description |
|---|---|---|---|
serviceAccount.create |
boolean | true |
Create a dedicated ServiceAccount. |
serviceAccount.automountServiceAccountToken |
boolean | false |
Mount Kubernetes API credentials. |
networkPolicy.enabled |
boolean | true |
Create component-aware NetworkPolicy rules. |
networkPolicy.ingressFrom |
array | [] |
Sources allowed to access the CKAN listener. |
networkPolicy.egress |
array | [{}] |
Complete egress rule list; replace per environment. |
Backup
The S3 backup CronJob runs pg_dump on the CKAN PostgreSQL database. Dataset files and resource uploads stored in the
/var/lib/ckan PVC are not included in the backup. Use Velero, NFS snapshots, or storage provider snapshots to
protect the PVC data.
| Parameter | Type | Default | Description |
|---|---|---|---|
backup.enabled |
boolean | false |
Enable scheduled pg_dump S3 backup. |
backup.schedule |
string | "0 3 * * *" |
Cron schedule. |
backup.s3.endpoint |
string | "" |
S3-compatible endpoint URL. |
backup.s3.bucket |
string | "" |
Target bucket name. |
backup.s3.existingSecret |
string | "" |
Existing secret with S3 credentials. |
extraManifests |
array | [] |
Extra Kubernetes manifests. |
Upgrade Notes
CKAN 2.12.0 is a minor application upgrade. Back up the CKAN database and
persistent storage, review the
upstream 2.12.0 release,
and test extensions against CKAN 2.12 before production rollout. Keep Solr,
PostgreSQL, and Redis available while CKAN performs its upgrade steps.
The former redisConfig.external.url value was replaced with structured
connection fields and a Secret-backed password. The structured external Redis
configuration enables TLS by default. Operators migrating a plaintext
redis:// endpoint must set redisConfig.external.tls: false; TLS-enabled
endpoints can retain the default.