Documentation / couchdb
CouchDB
Installs Apache CouchDB, the document database: a single node, or a cluster in CouchDB’s own clustering, in which every database is divided into shards and each shard is kept in copies on different nodes.
The chart provides:
- one node or several, with a node count that can be changed on a running release: copies of every shard are moved, checked and only then removed;
- the administrator taken from a Secret you create, which the chart never creates, changes or prints, and a valid user required on every request;
- Erlang distribution between the nodes over TLS, with a certificate authority of the release’s own made with cert-manager, and optional HTTPS for clients;
- databases, their security and users declared in values;
- logical backups of every database to S3-compatible storage on a schedule, and restore into a new release;
- continuous replication between releases, such as one instance’s regions, each pulling from its peers as a replication user of its own;
- optional access from outside the cluster through Kong with an allow-list, for the HTTP API, for Fauxton, CouchDB’s web interface, and for peers’ replication;
- volumes encrypted with LUKS by Longhorn;
- a NetworkPolicy and Prometheus metrics;
- the official image from the Mamluk registry, pinned by digest, and every
Pod meeting the
restrictedPod Security Standard.
Prerequisites
- Kubernetes 1.33 or later.
- cert-manager, for the certificates between the nodes. It is included with the Bahriya Kubernetes Engine; on other Kubernetes, install it first.
- A pull secret for
1x.axnamed1x-axin the release’s namespace, created from an image token as described in the access documentation. - A StorageClass for the volumes: the cluster’s default, or one named in
persistence.storageClass. Raising the volume size later needs a class that allows volume expansion. - As many nodes of the Kubernetes cluster as CouchDB nodes: no two CouchDB nodes are placed on one node of the cluster.
-
A Secret holding the administrator’s name and password, created before the release:
kubectl --namespace docs create secret generic couchdb-admin \ --from-literal=username=admin --from-file=password=./password
Installation
helm install docs tersane/couchdb \
--namespace docs --create-namespace \
--set auth.existingSecret=couchdb-admin \
--version <chart version>
A single node:
helm install docs tersane/couchdb --namespace docs \
--set auth.existingSecret=couchdb-admin \
--set replicas=1 --set cluster.copies=1
The release’s resources are named <release>-couchdb (here docs-couchdb),
or after the release alone if its name already contains couchdb. Check that
every node is ready:
kubectl --namespace docs get pods -l app.kubernetes.io/instance=docs
Connecting
Clients in the release’s namespace reach http://docs-couchdb:5984, or
https://docs-couchdb:6984 with tls.enabled. Every request needs a valid
user; only /_up answers without one.
kubectl --namespace docs port-forward svc/docs-couchdb 5984
curl -u admin http://localhost:5984/_membership
_membership lists every node in cluster_nodes and, when all are
connected, in all_nodes.
Databases and users
Databases, who may use them, and users can be declared in values; a Job creates or updates them after each install and upgrade. Each user’s password comes from a Secret you create:
databases:
- name: orders
members:
names: [orders-app]
users:
- name: orders-app
existingSecret: orders-app-couchdb # key: password
Leave both empty to manage databases and users yourself, through the HTTP API.
Shards and copies
cluster.shards (CouchDB’s q, default 2) and cluster.copies (CouchDB’s
n, default 3) set how databases created afterwards are divided. A database
can set its own, as shards and copies in databases, or as q and n on
the request that creates it. With three nodes and three copies, every
database stays readable and writable while one node is down. copies may not
exceed replicas.
Resizing
Change replicas and upgrade. Take a backup first (see Backups); a
resize moves data between nodes.
- Adding nodes. The new nodes join the cluster, and a Job then moves copies of shards onto them until every node holds a similar share.
- Removing nodes. Before the StatefulSet shrinks, a Job moves every copy off the nodes that will be removed, checks that each new copy holds as many documents as the copy it was made from, and checks that every database answers in full with the departing nodes in maintenance mode. Only then does the upgrade continue; the removed nodes’ volumes are deleted with them.
Each Job prints the shard maps before and after:
kubectl --namespace docs logs job/docs-couchdb-scale-in
A removal is refused, with nothing changed, when a database has more copies
of a shard than the nodes that would remain (lower the database’s copies
first, or keep more nodes), or when the copies are not filled within
resize.timeoutSeconds; the copies added are then removed again. A refused
upgrade leaves the release as it was; correct the cause and upgrade again.
Backups
backup:
enabled: true
bucket: my-bucket
basePath: couchdb/docs
endpoint: s3.example.com # empty for Amazon S3
existingSecret: docs-s3 # keys access-key and secret-key
schedule: "0 2 * * *"
timezone: UTC
retentionDays: 30
Each backup is a directory named after its time, such as 20261108T020000Z,
holding every database’s documents, with their revisions, attachments and
design documents, and its security object. manifest.json is written last; a
directory without it is incomplete and is never restored. Deleted documents
and conflicting revisions are not kept. Backups older than retentionDays
are deleted, except the most recent.
To take a backup now:
kubectl --namespace docs create job docs-backup-now --from=cronjob/docs-couchdb-backup
The platform layout
With backup.layout: platform every storage setting comes from one Secret,
and each region of an instance keeps its backups under its own path:
clusterRegion: helsinki-1
backup:
enabled: true
layout: platform
s3:
existingSecret: platform-backup-s3 # S3_ACCESS_KEY, S3_SECRET_KEY, S3_HOST, S3_BUCKET, S3_REGION
prefix: couchdb/acme/shop/docs
The backups are then under couchdb/acme/shop/docs/helsinki-1/ in the
Secret’s bucket. The render fails without the Secret, the prefix or
clusterRegion.
Restore
A backup is restored into a new release at install, with every document’s id and revision kept, so that replication with the original database still recognises them:
helm install docs2 tersane/couchdb --namespace docs \
--set auth.existingSecret=couchdb-admin \
--set restore.enabled=true --set restore.backup=20261108T020000Z \
--set backup.bucket=my-bucket --set backup.basePath=couchdb/docs \
--set backup.existingSecret=docs-s3
restore.backup: latest takes the most recent complete backup, and
restore.databases restores only the databases listed. A database that
already holds documents other than design documents is refused. The install
fails if the backup cannot be restored; the Job’s log says why.
In the platform layout the backup is read from
<restore.prefix>/<clusterRegion>, where restore.prefix defaults to
backup.s3.prefix; set it to another instance’s prefix to restore that
instance’s backup into a new one.
TLS
The nodes always authenticate one another with certificates from the
release’s own authority; interNodeTls.enabled: false turns this off and
sends their traffic unencrypted.
For clients, tls.enabled adds HTTPS on port 6984, with a certificate from
tls.existingSecret (keys tls.crt, tls.key and ca.crt) or one issued by
the release’s authority. Clients verify the server with ca.crt from the
Secret <release>-couchdb-http-certs.
tls.httpEnabled: false removes plain HTTP on 5984 from the Service and the
NetworkPolicy. CouchDB binds HTTP and HTTPS to one address, so it still
listens on 5984 inside the Pod: the NetworkPolicy is what refuses other Pods,
and the chart requires networkPolicy.enabled for this setting. It relies on
a network plugin that enforces NetworkPolicy.
External access
exposure:
external:
enabled: true
host: docs.example.com
tlsSecret: docs-example-com-tls
ipAllowlist: [203.0.113.0/24]
Kong terminates HTTPS on the host name and admits only the listed addresses. The list may not be empty.
Host names can also be composed, and their certificates issued by cert-manager:
clusterRegion: helsinki-1
exposure:
defaultHostname: docs-x1
domain: couchdb.example.com
certIssuer: public-issuer # a ClusterIssuer
external:
enabled: true # docs-x1.helsinki-1.couchdb.example.com
ipAllowlist: [203.0.113.0/24]
Fauxton
client:
enabled: true # ui-docs-x1.helsinki-1.couchdb.example.com
ipAllowlist: [203.0.113.7/32]
expiresAt: "2026-11-12T10:00:00Z" # informational, an annotation
CouchDB’s web interface, at /_utils/ on its own host name. Fauxton runs in
the browser and calls CouchDB’s HTTP API, so the host passes the whole API to
the listed addresses; CouchDB still requires a valid user. The allow-list may
not be empty: the render fails rather than publish CouchDB to everyone.
Replication between releases
Releases holding the same databases, such as one instance’s regions, can keep them in step with CouchDB’s own continuous replication. Each release pulls from its peers:
databases:
- name: orders
replication:
user:
existingSecret: orders-replicator # this release's replication user
peers:
- name: falkenstein-1
url: https://repl-docs-x1.falkenstein-1.couchdb.example.com
existingSecret: orders-replicator # the peer's replication user
ingress:
enabled: true # repl-docs-x1.helsinki-1.couchdb.example.com
ipAllowlist: [192.0.2.10, 2001:db8::10]
requestsPerMinute: 1200
After each install and upgrade a Job writes one document in _replicator
for every peer and every replicated database (replication.databases, or
every database in databases), each pulling the peer’s copy into this one,
and deletes the ones of peers or databases no longer listed. It manages only
documents whose id starts with bahriya-. Set replication.reconcile: true
so that removing the last peer also deletes its documents.
The setup Job creates the replication user and makes it a member of every
replicated database; peers authenticate as that user, never as the
administrator. replication.ingress publishes the release to its peers on
its own host name, to the listed addresses only, with a limit on requests per
minute from each address. Peers’ certificates are verified against the
image’s public authorities unless replication.verifyPeer is false.
Replication is multi-primary: each release accepts writes. When the same
document is changed in two releases before they replicate, CouchDB keeps
both revisions, chooses the same winner in each, and lists the other in
_conflicts; resolving the conflict is the application’s.
Encryption at rest
encryption:
volume:
enabled: true
Longhorn encrypts every volume of the release with LUKS, using a passphrase
in the Secret <release>-couchdb-volume-encryption, generated at install
unless encryption.volume.passphrase or existingSecret gives one, and kept
when the release is uninstalled, as the volumes are. A passphrase that
differs from the one the volumes were encrypted with is refused. CouchDB
itself has no encryption at rest. Decide at install: the StorageClass of an
existing release’s volumes cannot change. Needs Longhorn.
Network policy
Admits the HTTP API from Pods in the release’s namespace, from the chart’s
Jobs and test, from the sources in networkPolicy.from, and from the ingress
controller’s namespace when external access, Fauxton or the replication host
is on; admits the nodes to one another on 4369 and 9100; and admits the
metrics port from networkPolicy.metrics.from, or every source when that is
empty.
Outgoing traffic is not restricted unless networkPolicy.egress.enabled:
then the nodes reach DNS, the release’s Pods, HTTPS anywhere (for peers’
replication hosts; egress.https: false removes it) and the destinations in
egress.to. The Jobs stay unrestricted, since backups reach object storage.
Metrics
CouchDB’s Prometheus metrics are on port 17986 at
/_node/_local/_prometheus, without authentication, with the
prometheus.io/* annotations on the Pods, and a ServiceMonitor with
metrics.serviceMonitor.enabled.
Storage
persistence.size can be raised on a running release: a Job asks for the
larger size claim by claim, which the StorageClass must allow. It can never
be lowered; the upgrade is refused.
persistentVolumeClaimRetentionPolicy decides whether a volume is deleted
when resizing removes its node (whenScaled, Delete by default) and when
the release is uninstalled (whenDeleted, Retain by default).
Upgrades
The image is pinned by digest in each chart version. A new chart version restarts the nodes one at a time; with three or more nodes and three copies, the cluster stays available.
Upgrading from the previous release changes nothing with the same values: every addition is off by default. The nodes restart once, since the Jobs’ scripts moved to a ConfigMap of their own; after that, a change to a script no longer restarts the nodes. Upgrading within CouchDB 3 needs no other step: the data files are compatible, and views are rebuilt when first queried after a change of their design document. Compaction is optional and can be run afterwards.
Pod Security
Every Pod meets the restricted Pod Security Standard: CouchDB runs as user
5984 and the Jobs as 10001, with a read-only root filesystem, no privilege
escalation, every capability dropped and the runtime’s default seccomp
profile.
Testing
helm test docs --namespace docs
The test checks that an anonymous request is refused, and writes a document in a new database as the administrator, reads it back with its revision and deletes the database.
Uninstalling
helm uninstall docs --namespace docs
The volumes are kept; delete them to remove the data:
kubectl --namespace docs delete pvc -l app.kubernetes.io/instance=docs
Parameters
CouchDB
| Parameter | Description | Default |
|---|---|---|
replicas |
Nodes: 1 is a single node | 3 |
cluster.shards / cluster.copies |
Shards and copies of databases created afterwards | 2 / 3 |
image.repository / image.tag |
CouchDB, the official image | 1x.ax/tersane/core/couchdb/couchdb / the release’s |
toolsImage.repository / toolsImage.tag |
The Jobs’ and test’s image | 1x.ax/tersane/core/couchdb/couchdb-backup / the release’s |
imagePullSecrets |
Pull secrets of every Pod | [{name: 1x-ax}] |
auth.existingSecret |
The Secret with the administrator’s name and password; required | "" |
auth.usernameKey / auth.passwordKey |
Its keys | username / password |
auth.cookieSecret / auth.cookieKey |
A Secret with the Erlang cookie; empty generates one | "" / cookie |
config |
CouchDB settings, as INI sections | {} |
clusterDomain |
The cluster’s DNS domain | cluster.local |
clusterRegion |
The region served, for the platform backup layout and composed host names | "" |
TLS
| Parameter | Description | Default |
|---|---|---|
interNodeTls.enabled |
Distribution between the nodes over TLS | true |
tls.enabled |
HTTPS for clients on 6984 | false |
tls.existingSecret |
A Secret with the HTTPS certificate; empty issues one | "" |
tls.httpEnabled |
Plain HTTP on 5984 in the Service and NetworkPolicy | true |
tls.extraDnsNames |
Names the HTTPS certificate carries besides the Service’s | [] |
Storage, resources and scheduling
| Parameter | Description | Default |
|---|---|---|
persistence.size / .storageClass |
Volume size per node; StorageClass | 10Gi / the default class |
persistentVolumeClaimRetentionPolicy.whenScaled / .whenDeleted |
Volumes on resizing; on uninstall | Delete / Retain |
encryption.volume.enabled |
LUKS-encrypted volumes, by Longhorn | false |
encryption.volume.passphrase / .existingSecret |
The passphrase, or a Secret with it; empty generates one | "" |
encryption.volume.storageClass.* |
reclaimPolicy, numberOfReplicas, dataLocality of the encrypted class |
Delete, 3, disabled |
resources |
Requests and limits of each node | 250m/512Mi, limits 1000m/1Gi |
jobResources |
Of the init container, the Jobs and the test | 10m/32Mi, limits 500m/256Mi |
podDisruptionBudget.enabled / .maxUnavailable |
A budget for node drains | true / 1 |
topologySpread.topologyKey / .whenUnsatisfiable |
How nodes are spread | kubernetes.io/hostname / DoNotSchedule |
nodeSelector, tolerations, priorityClassName |
Scheduling | none |
terminationGracePeriodSeconds |
60 |
|
maxOpenFiles |
Files each node keeps open | 65536 |
Databases, users and resizing
| Parameter | Description | Default |
|---|---|---|
databases |
Databases with their members and admins | [] |
users |
Users, each with a Secret holding its password | [] |
resize.timeoutSeconds |
How long a resize waits for copies to fill | 1800 |
Backups and restore
| Parameter | Description | Default |
|---|---|---|
backup.enabled |
Scheduled backups | false |
backup.layout |
legacy (the settings below) or platform (one Secret, <prefix>/<clusterRegion>) |
legacy |
backup.s3.existingSecret / .prefix |
The platform layout’s Secret and instance prefix | "" |
backup.bucket / .basePath |
Where backups are kept | "" |
backup.endpoint / .region |
S3-compatible storage other than Amazon S3; region | "" / us-east-1 |
backup.existingSecret |
A Secret with access-key and secret-key |
"" |
backup.schedule / .timezone |
When backups are taken | 0 2 * * * / UTC |
backup.retentionDays |
Days a backup is kept | 30 |
backup.exclude |
Databases not backed up | [] |
restore.enabled |
Restore a backup at install | false |
restore.backup |
The backup’s directory, or latest |
latest |
restore.bucket / .basePath |
Where to restore from, when not backup’s |
"" |
restore.prefix |
The instance prefix to restore from in the platform layout | backup.s3.prefix |
restore.databases |
The databases to restore; empty for all | [] |
Access and metrics
| Parameter | Description | Default |
|---|---|---|
exposure.defaultHostname / .domain |
Compose host names as [ui-\|repl-]<defaultHostname>.<clusterRegion>.<domain> |
"" |
exposure.certIssuer |
A ClusterIssuer for every host’s certificate | "" |
exposure.external.enabled |
The HTTP API on a host name through Kong | false |
exposure.external.host / .tlsSecret |
Host name (or composed); its TLS Secret | "" |
exposure.external.ipAllowlist |
Addresses admitted; required | [] |
exposure.external.ingressClassName / .ingressNamespace |
The ingress controller, for every host | kong / kong |
client.enabled |
Fauxton, at /_utils/ on its own host |
false |
client.host / .tlsSecret |
Its host name (or composed); its TLS Secret | "" |
client.ipAllowlist |
Addresses admitted; required, never empty | [] |
client.expiresAt |
When the platform switches it off; an annotation | "" |
replication.peers |
Peers to pull from: name, url, existingSecret |
[] |
replication.databases |
Databases replicated; empty for every database in databases |
[] |
replication.user.existingSecret |
This release’s replication user, which peers pull as | "" |
replication.reconcile |
Run the replication Job with no peers, to delete removed peers’ documents | false |
replication.verifyPeer |
Verify peers’ certificates | true |
replication.ingress.enabled |
The host peers pull from | false |
replication.ingress.host / .tlsSecret |
Its host name (or composed); its TLS Secret | "" |
replication.ingress.ipAllowlist |
The peers’ addresses; required, never empty | [] |
replication.ingress.requestsPerMinute |
Requests a minute from each address | 1200 |
networkPolicy.enabled |
The NetworkPolicy | true |
networkPolicy.from |
Further sources admitted to the HTTP API | [] |
networkPolicy.metrics.from |
Sources admitted to the metrics port; empty admits every source | [] |
networkPolicy.egress.enabled / .https / .to |
Restrict the nodes’ outgoing traffic; HTTPS anywhere; further destinations | false / true / [] |
metrics.enabled |
The Prometheus port | true |
metrics.annotations |
prometheus.io annotations on the Pods |
true |
metrics.serviceMonitor.enabled |
A ServiceMonitor | false |