Appwrite
Deploy Appwrite on Kubernetes — a self-hosted Backend-as-a-Service (BaaS) platform providing authentication, databases, storage, functions, messaging, and a real-time API. Ships as a multi-workload chart (~20 Kubernetes Deployments) with bundled MariaDB and Redis.
appwrite.openSslKeyV1 is a 64-character hex key used to encrypt OAuth provider secrets, API keys, and sensitive
credentials stored in the database. If this key changes or is lost, all previously encrypted values become
permanently unreadable. Always store it in a Kubernetes Secret via appwrite.existingSecret and never rotate it
in production.
Key Features
- Full Appwrite stack — API, Console IV 1.1.159-self-hosted, Realtime WebSocket, 15 workers, schedulers, maintenance
- ~20 Kubernetes workloads — each component runs independently and scales separately
- MariaDB + Redis — bundled subcharts or external connections
- Shared PVCs — uploads, cache, certificates, functions, builds, and sites shared across pods
- Root DB access required — Appwrite needs MariaDB root credentials for schema migrations
- Ingress routing — Console at
/, API at/v1, Realtime WebSocket at/v1/realtime - Side-effect-free health probes — API health uses the public
/v1/health/versionendpoint - mysqldump backup — scheduled CronJob to S3
The workers.functions and schedulers.functions workloads are deployed, but serverless function execution requires
a separate executor/orchestrator and a ClickHouse execution database, configured through
_APP_CONNECTIONS_DB_EXECUTIONS. These services are not included in this chart. Function invocations require those
dependencies; a running worker alone does not provide execution support.
Installation
HTTPS repository:
helm repo add helmforge https://repo.helmforge.dev
helm repo update
helm install appwrite helmforge/appwrite -f values.yaml
OCI registry:
helm install appwrite oci://ghcr.io/helmforgedev/helm/appwrite -f values.yaml
Deployment Examples
# values.yaml — Appwrite with bundled MariaDB and Redis
appwrite:
domain: appwrite.example.com
# Generate: openssl rand -hex 32
openSslKeyV1: 'your-64-char-hex-key-here'
mariadb:
enabled: true
auth:
password: 'strong-db-password'
rootPassword: 'strong-root-password'
standalone:
persistence:
enabled: true
size: 20Gi
redis:
enabled: true
auth:
enabled: true
password: 'strong-redis-password'
persistence:
enabled: true
uploads:
size: 20Gi
functions:
size: 10Gi
builds:
size: 10Gi
ingress:
enabled: true
ingressClassName: traefik
hosts:
- host: appwrite.example.com
paths:
- path: /
pathType: Prefix# values.yaml — Appwrite with external MariaDB and Redis
# IMPORTANT: External MariaDB needs root credentials for Appwrite schema migrations
appwrite:
domain: appwrite.example.com
existingSecret: appwrite-master-secrets # contains openssl key + JWT secret
existingSecretOpenSslKey: appwrite-openssl-key
existingSecretJwtKey: appwrite-jwt-secret
mariadb:
enabled: false
database:
mode: external
external:
host: mariadb.database.svc.cluster.local
port: 3306
name: appwrite
rootUser: root
existingSecret: appwrite-mariadb-root # needs ROOT password for migrations
existingSecretPasswordKey: mariadb-root-password
redis:
enabled: false
cache:
mode: external
external:
host: redis.cache.svc.cluster.local
port: 6379
existingSecret: appwrite-redis-credentials
existingSecretPasswordKey: redis-password
persistence:
enabled: true
uploads:
size: 20Gi
ingress:
enabled: true
ingressClassName: traefik
hosts:
- host: appwrite.example.com
paths:
- path: /
pathType: Prefix# values.yaml — Production Appwrite with SMTP, HTTPS, and backup
appwrite:
domain: appwrite.example.com
existingSecret: appwrite-master-secrets
smtp:
host: smtp.mailgun.org
port: '587'
secure: tls
username: [email protected]
existingSecret: appwrite-smtp-credentials
existingSecretPasswordKey: smtp-password
mariadb:
enabled: true
auth:
password: 'strong-db-password'
rootPassword: 'strong-root-password'
standalone:
persistence:
enabled: true
size: 50Gi
redis:
enabled: true
auth:
enabled: true
password: 'strong-redis-password'
standalone:
persistence:
enabled: true
size: 4Gi
persistence:
enabled: true
uploads:
size: 50Gi
functions:
size: 20Gi
builds:
size: 20Gi
sites:
size: 20Gi
backup:
enabled: true
schedule: '0 3 * * *'
s3:
endpoint: https://s3.amazonaws.com
bucket: appwrite-backups
existingSecret: appwrite-s3-backup-credentials
ingress:
enabled: true
ingressClassName: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: appwrite.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: appwrite-tls
hosts:
- appwrite.example.comUpgrade Notes
Appwrite 2.3.0
The 2.3 release
removes development keys, consolidates generated Console links under
_APP_CONSOLE_URL, and ships Console 1.1.159-self-hosted. Its migration touches
every project’s users and identities collections, removes the devKeys
collection and strips obsolete dev-key scopes; run migrate after upgrading.
Remove _APP_CONSOLE_URL_SCHEME from retained environment values. When the
Console uses a different host from the API, set _APP_CONSOLE_URL to the full
Console origin through extraEnv before upgrading.
Function and site executions now require ClickHouse through
_APP_CONNECTIONS_DB_EXECUTIONS; they are no longer read from the project
MariaDB database. Provision that external backend before enabling execution
features. Preserve/export legacy execution history before upgrading: records
stored only in the old project database will no longer be listed. This chart
does not provision the executor/orchestrator or migrate historical executions.
Remove _APP_EXECUTIONS_DUAL_WRITE, _APP_MAINTENANCE_RETENTION_USAGE_HOURLY
and _APP_CONNECTIONS_DB_LOGS from retained environment values. Usage retention
belongs to the external ClickHouse deployment.
Earlier 2.1.0 changes
The 2.1 release fixes
password-protected Redis queue publishers and consumers, including ACL users and
reserved characters in passwords. It adds the S3-compatible API at /v1/s3 using
project API keys; the existing storage backend and bucket permissions still apply.
Automatic crop gravity remains unconfigured unless an external service is provided
through _APP_AUTOGRAVITY_HOST. This chart does not deploy that optional service.
Error reporting now supports only sentry:// DSNs through
appwrite.logging.sentryDsn. appwrite.logging.format selects pretty or json
container logs. The old logging.provider field is retained only for compatible
empty/default/sentry values and no longer emits the removed environment variable.
Remove _APP_LOGGING_PROVIDER, _APP_LOGGING_CONFIG_REALTIME and experimental
logging provider/config variables from custom environment values before upgrading.
The bundled Redis chart is 3.0.0, retaining its image, credentials and storage.
The following 2.0 migration requirements also apply when upgrading directly from 1.9.x.
Back up MariaDB, all shared PVCs and the application Secret before upgrading from 1.9.x. Keep traffic paused during the upgrade and migration. This chart retains MariaDB explicitly; the upstream installer’s new PostgreSQL default does not move existing data between engines.
Use helm upgrade <release> <chart-ref> --reset-then-reuse-values (or explicitly set the new image tag),
then run kubectl exec -n <namespace> deploy/<api-deployment> -c api -- migrate.
Verify the API, worker logs and existing projects before restoring traffic. To
roll back to 2.2, stop new queue receives and drain or recover every in-flight
reservation before removing the 2.3 workers. Then restore the database, volumes,
Secret and matching old images together.
Console IV uses appwrite/new:1.1.159-self-hosted on port 3000 with the API on the same origin;
the console Service still exposes port 80. worker-audits was removed upstream:
set workers.audits.enabled=false in retained values. Jobs, screenshots,
executions and notifications now have separate worker toggles, alongside the
remaining workers. Disabled workers do not process their respective queues.
Generated encryption and JWT Secret entries are retained on upgrade. Supplying a
new appwrite.openSslKeyV1 overrides the retained key and requires an application
key-rotation procedure; it is not an automatic data re-encryption mechanism.
DocumentsDB, VectorsDB and embeddings remain disabled. This chart does not provision the extra engines, embedding server, executor or orchestrator needed for those features and Functions/Sites execution. Provision and configure those dependencies separately before opting in through extraEnv.
The stats-resources task is deployed only when appwrite.usageStats=enabled
and tasks.statsResources.enabled=true; upstream exits immediately when usage
is disabled. This avoids restarting an intentionally inactive process.
Usage statistics now require ClickHouse. appwrite.usageStats defaults to
disabled; to retain or enable usage reporting, provision a private ClickHouse
service (the HelmForge ClickHouse chart can provide it) and supply its TLS-enabled HTTP DSN
through a Secret:
appwrite:
usageStats: enabled
extraEnv:
- name: _APP_CONNECTIONS_DB_USAGE
valueFrom:
secretKeyRef:
name: appwrite-usage
key: dsn
- name: _APP_CONNECTIONS_DB_EXECUTIONS
valueFrom:
secretKeyRef:
name: appwrite-executions
key: dsn
Use a DSN such as https://<user>:<password>@clickhouse.example.com:8443/appwrite?secure=true.
The secure=true query parameter enables TLS in the Appwrite ClickHouse adapter;
the scheme alone is insufficient. URL-encode credentials containing reserved
characters. Use a DNS hostname matching the server certificate and a certificate
chain trusted by the Appwrite container. The client verifies peers and hostnames;
for a private CA, install its root into the container trust store before deployment. In 2.2, usage-setup initializes and
checks both usage and execution schemas, so configure both connections before
running it in the API pod. Verify schema readiness before enabling traffic. Plan retention of historical
usage data separately from new usage schema setup. Back up external ClickHouse
independently; the chart S3 backup covers the core MariaDB and shared-volume data.
Configuration Reference
Core
| Parameter | Type | Default | Description |
|---|---|---|---|
nameOverride |
string | "" |
Override the chart name. |
fullnameOverride |
string | "" |
Override the full release name. |
commonLabels |
object | {} |
Extra labels added to all resources. |
Images
| Parameter | Type | Default | Description |
|---|---|---|---|
image.repository |
string | docker.io/appwrite/appwrite |
Appwrite server image. |
image.tag |
string | "2.3.0" |
Appwrite image tag. |
console.image.repository |
string | docker.io/appwrite/new |
Console image. |
console.image.tag |
string | "1.1.159-self-hosted" |
Console image tag. |
Appwrite Configuration
| Parameter | Type | Default | Description |
|---|---|---|---|
appwrite.domain |
string | "" |
Public domain. Auto-detected from Ingress if empty. |
appwrite.openSslKeyV1 |
string | "" |
64-char hex encryption key. Auto-generated if empty. Never rotate. |
appwrite.existingSecret |
string | "" |
Existing secret with the OpenSSL key and JWT secret. |
appwrite.existingSecretOpenSslKey |
string | appwrite-openssl-key |
Key for the OpenSSL encryption key in the existing secret. |
appwrite.existingSecretJwtKey |
string | appwrite-jwt-secret |
Key for the JWT secret in the existing secret. |
appwrite.locale |
string | en |
Appwrite locale (ISO 639-1). |
appwrite.usageStats |
string | disabled |
Requires an external ClickHouse DSN. |
appwrite.graphql |
string | enabled |
GraphQL API. |
appwrite.storage.limit |
integer | 30000000 |
Maximum file upload size in bytes (default 30 MB). |
appwrite.functions.timeout |
integer | 900 |
Maximum function execution timeout in seconds. |
appwrite.extraEnv |
array | [] |
Extra environment variables applied to all Appwrite pods. |
Logging
| Parameter | Type | Default | Description |
|---|---|---|---|
appwrite.logging.format |
string | pretty |
Container log format, pretty or json. |
appwrite.logging.sentryDsn |
string | "" |
Error reporting DSN, must use sentry://. |
appwrite.logging.provider |
string | "" |
Deprecated compatibility field; empty/default/sentry only, no provider environment variable emitted. |
SMTP
| Parameter | Type | Default | Description |
|---|---|---|---|
appwrite.smtp.host |
string | "" |
SMTP server hostname. |
appwrite.smtp.port |
string | "" |
SMTP server port. |
appwrite.smtp.secure |
string | "" |
SMTP security mode (tls, ssl, or empty). |
appwrite.smtp.username |
string | "" |
SMTP username. |
appwrite.smtp.existingSecret |
string | "" |
Existing secret with SMTP password. |
appwrite.smtp.existingSecretPasswordKey |
string | smtp-password |
Key for the SMTP password. |
Services
Each service can be scaled independently:
| Component | Parameter | Default | Description |
|---|---|---|---|
| API | api.replicaCount |
1 |
API server replicas. |
| Realtime | realtime.replicaCount |
1 |
Realtime WebSocket server replicas. |
Workers
The 15 supported workers are enabled by default at replicaCount: 1. The table
also lists the legacy workers.audits key, which must remain disabled.
| Worker | Key | Description |
|---|---|---|
audits |
workers.audits |
Removed upstream; must remain disabled. |
jobs |
workers.jobs |
Background jobs. |
screenshots |
workers.screenshots |
Screenshot generation. |
executions |
workers.executions |
Execution processing. |
notifications |
workers.notifications |
Notification processing. |
webhooks |
workers.webhooks |
Outbound webhook delivery. |
deletes |
workers.deletes |
Soft-delete cleanup. |
databases |
workers.databases |
Database event processing. |
builds |
workers.builds |
Function build jobs. |
certificates |
workers.certificates |
SSL certificate management. |
functions |
workers.functions |
Function invocation worker. |
mails |
workers.mails |
Email delivery. |
messaging |
workers.messaging |
SMS/push notification delivery. |
migrations |
workers.migrations |
Data migration worker. |
statsResources |
workers.statsResources |
Resource usage statistics. |
statsUsage |
workers.statsUsage |
API usage statistics. |
Each supported worker accepts enabled, replicaCount, and resources fields.
Database
Appwrite schema setup and the explicit upgrade migration require MariaDB root access. Unlike typical applications,
providing only an app-level user is not sufficient. Supply the root password via database.external.existingSecret.
| Parameter | Type | Default | Description |
|---|---|---|---|
database.mode |
string | auto |
Database mode: auto or external. |
database.external.host |
string | "" |
External MariaDB hostname. |
database.external.rootUser |
string | root |
Root username for schema migrations. |
database.external.existingSecret |
string | "" |
Existing secret with root password. |
database.external.existingSecretPasswordKey |
string | mariadb-root-password |
Key for the root password in the existing secret. |
Persistence
All PVCs are shared across workers and the API. If api.replicaCount > 1, the storage class
must support ReadWriteMany.
| Parameter | Type | Default | Description |
|---|---|---|---|
persistence.enabled |
boolean | true |
Create shared PVCs for all Appwrite data. |
persistence.storageClass |
string | "" |
StorageClass. Use RWX class for multi-replica setups. |
persistence.accessModes |
array | [ReadWriteOnce] |
PVC access modes. |
persistence.uploads.size |
string | 10Gi |
Uploads storage. |
persistence.cache.size |
string | 2Gi |
Cache storage. |
persistence.certificates.size |
string | 1Gi |
SSL certificate storage. |
persistence.functions.size |
string | 5Gi |
Function code storage. |
persistence.builds.size |
string | 5Gi |
Function build storage. |
persistence.sites.size |
string | 5Gi |
Sites/hosting storage. |
Backup
| Parameter | Type | Default | Description |
|---|---|---|---|
backup.enabled |
boolean | false |
Enable scheduled mysqldump 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. |
backup.database.mysqldumpArgs |
string | --single-transaction --routines --triggers |
Extra mysqldump arguments. |
Ingress
| Parameter | Type | Default | Description |
|---|---|---|---|
ingress.enabled |
boolean | false |
Enable an Ingress resource. |
ingress.ingressClassName |
string | traefik |
Ingress class name. |
ingress.annotations |
object | {} |
Annotations for the Ingress. |
ingress.hosts |
array | — | Host and path rules. Console: /, API: /v1. |
ingress.tls |
array | [] |
TLS configuration. |