Apache Answer
Deploy Apache Answer on Kubernetes — an open-source Q&A platform for teams and communities. Supports SQLite (zero-configuration default), PostgreSQL, and MySQL, with automatic unattended installation and database-aware scheduled backups.
With the default configuration (database.mode: auto and no subchart enabled), Answer uses SQLite stored in the
/data PVC. No external database is needed for teams under a few hundred users. Migrate to PostgreSQL or MySQL when
you need concurrent write performance or replication.
Key Features
- Three database backends — SQLite (default), PostgreSQL, MySQL
- Auto-detection — mode is inferred from which subchart or external host is configured
- Auto-install — unattended first-boot setup via environment variables (
autoInstall: true) - Database-aware backup —
tarfor SQLite,pg_dumpfor PostgreSQL,mysqldumpfor MySQL - Admin credentials —
existingSecretfor GitOps-safe secret management - Independent ports — configure the real Answer listener separately from the Service port
- Hardened defaults — non-root execution, read-only root filesystem, disabled API token, and NetworkPolicy
Installation
HTTPS repository:
helm repo add helmforge https://repo.helmforge.dev
helm repo update
helm install answer helmforge/answer -f values.yaml
OCI registry:
helm install answer oci://ghcr.io/helmforgedev/helm/answer -f values.yaml
Deployment Examples
# values.yaml — Apache Answer with SQLite (zero database configuration)
answer:
siteName: 'My Q&A'
siteUrl: 'https://qa.example.com'
language: en-US
autoInstall: true
admin:
name: admin
email: [email protected]
existingSecret: answer-admin-credentials # key: admin-password
existingSecretPasswordKey: admin-password
persistence:
enabled: true
size: 10Gi # covers both SQLite file (/data/answer.db) and uploads
ingress:
enabled: true
ingressClassName: traefik
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: qa.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: answer-tls
hosts:
- qa.example.com# values.yaml — Apache Answer with bundled PostgreSQL
answer:
siteName: 'My Q&A'
siteUrl: 'https://qa.example.com'
autoInstall: true
admin:
name: admin
email: [email protected]
password: 'change-me'
postgresql:
enabled: true
auth:
database: answer
username: answer
password: 'strong-db-password'
standalone:
persistence:
enabled: true
size: 20Gi
persistence:
enabled: true
size: 10Gi # uploads only (SQLite not used when PostgreSQL is active)
ingress:
enabled: true
ingressClassName: traefik
hosts:
- host: qa.example.com
paths:
- path: /
pathType: Prefix# values.yaml — Apache Answer with bundled MySQL
answer:
siteName: 'My Q&A'
siteUrl: 'https://qa.example.com'
autoInstall: true
admin:
name: admin
email: [email protected]
password: 'change-me'
mysql:
enabled: true
auth:
database: answer
username: answer
password: 'strong-db-password'
rootPassword: 'strong-root-password'
standalone:
persistence:
enabled: true
size: 20Gi
persistence:
enabled: true
size: 10Gi
ingress:
enabled: true
ingressClassName: traefik
hosts:
- host: qa.example.com
paths:
- path: /
pathType: Prefix# values.yaml — Apache Answer with external PostgreSQL
answer:
siteName: 'My Q&A'
siteUrl: 'https://qa.example.com'
autoInstall: true
admin:
existingSecret: answer-admin-credentials
postgresql:
enabled: false
database:
mode: external
external:
vendor: postgres # or: mysql
host: postgres.database.svc.cluster.local
port: '5432'
name: answer
username: answer
existingSecret: answer-db-credentials
existingSecretPasswordKey: database-password
persistence:
enabled: true
size: 10Gi
backup:
enabled: true
schedule: '0 3 * * *'
s3:
endpoint: https://s3.amazonaws.com
bucket: answer-backups
existingSecret: answer-s3-credentialsConfiguration Reference
Core
| Parameter | Type | Default | Description |
|---|---|---|---|
global |
object | {} |
Values shared with chart dependencies. |
nameOverride |
string | "" |
Override the chart name. |
fullnameOverride |
string | "" |
Override the full release name. |
commonLabels |
object | {} |
Extra labels added to all resources. |
Image
| Parameter | Type | Default | Description |
|---|---|---|---|
image.repository |
string | docker.io/apache/answer |
Apache Answer container image. |
image.tag |
string | "2.0.3" |
Image tag. |
image.pullPolicy |
string | IfNotPresent |
Image pull policy. |
Answer Configuration
| Parameter | Type | Default | Description |
|---|---|---|---|
answer.siteUrl |
string | "" |
Full public URL. Auto-detected from Ingress if empty. |
answer.siteName |
string | Apache Answer |
Site name shown in browser tab and header. |
answer.language |
string | en-US |
Default UI language. |
answer.contactEmail |
string | [email protected] |
Contact email shown in the site footer. |
answer.autoInstall |
boolean | true |
Enable unattended first-boot setup using admin credentials from values. |
answer.logLevel |
string | INFO |
Log level: DEBUG, INFO, WARN, ERROR. |
answer.notifications.newQuestionEmail.queueSize |
integer | 1024 |
Maximum buffered new-question email tasks. |
answer.notifications.newQuestionEmail.sendIntervalSeconds |
integer | 0 |
Delay between new-question email attempts; 0 disables throttling. |
answer.extraEnv |
array | [] |
Extra environment variables for the container. |
Admin Credentials
| Parameter | Type | Default | Description |
|---|---|---|---|
admin.name |
string | admin |
Admin username for initial setup. |
admin.password |
string | "" |
Admin password. Auto-generated if empty. |
admin.email |
string | [email protected] |
Admin email address. |
admin.existingSecret |
string | "" |
Existing secret with admin credentials. |
admin.existingSecretPasswordKey |
string | admin-password |
Key inside the existing secret for the password. |
Database
Auto-detection precedence (database.mode: auto):
| Priority | Condition | Result |
|---|---|---|
| 1 | database.external.host or external.existingSecret set |
External DB |
| 2 | postgresql.enabled: true |
PostgreSQL subchart |
| 3 | mysql.enabled: true |
MySQL subchart |
| 4 | None of the above | SQLite (default) |
| Parameter | Type | Default | Description |
|---|---|---|---|
database.mode |
string | auto |
Database mode: auto, sqlite, external, postgresql, mysql. |
database.sqlite.file |
string | /data/answer.db |
SQLite file path inside the data volume. |
database.external.vendor |
string | postgres |
External DB vendor: postgres or mysql. |
database.external.host |
string | "" |
External database hostname. |
database.external.port |
string | "" |
External database port. Auto-detected from vendor if empty. |
database.external.name |
string | answer |
Database name. |
database.external.username |
string | answer |
Database username. |
database.external.existingSecret |
string | "" |
Existing secret with database password. |
database.external.existingSecretPasswordKey |
string | database-password |
Key for the password in the existing secret. |
Subcharts
| Parameter | Type | Default | Description |
|---|---|---|---|
postgresql.enabled |
boolean | false |
Deploy the bundled PostgreSQL subchart. |
postgresql.auth.database |
string | answer |
Database name. |
postgresql.auth.password |
string | "" |
Password. Auto-generated if empty. |
postgresql.primary.persistence.size |
string | 8Gi |
PVC size. |
mysql.enabled |
boolean | false |
Deploy the bundled MySQL subchart. |
mysql.auth.database |
string | answer |
Database name. |
mysql.auth.password |
string | "" |
Password. Auto-generated if empty. |
mysql.primary.persistence.size |
string | 8Gi |
PVC size. |
Persistence
| Parameter | Type | Default | Description |
|---|---|---|---|
persistence.enabled |
boolean | true |
Enable PVC for /data (SQLite DB file + uploads). |
persistence.size |
string | 5Gi |
PVC size. Increase if using SQLite as the primary database. |
persistence.storageClass |
string | "" |
StorageClass for the PVC. |
persistence.accessMode |
string | ReadWriteOnce |
PVC access mode. |
persistence.existingClaim |
string | "" |
Use an existing PVC. |
Backup
The backup CronJob detects which database is in use and runs the appropriate tool:
- SQLite →
tararchive of the full/datadirectory (Alpine image) - PostgreSQL →
pg_dump(PostgreSQL Alpine image) - MySQL →
mysqldump(MySQL image)
Only the images needed for the active database vendor are used.
PostgreSQL and MySQL backups contain only the database dump. They do not mount
or archive /data, which still holds uploads, configuration, and plugins. Back
up /data separately and restore it together with the SQL dump.
| Parameter | Type | Default | Description |
|---|---|---|---|
backup.enabled |
boolean | false |
Enable scheduled S3 backup CronJob. |
backup.schedule |
string | "0 3 * * *" |
Cron schedule. |
backup.archivePrefix |
string | answer |
Prefix for backup archive filenames. |
backup.images.sqlite |
string | alpine:3.22 |
Image for SQLite backup (tar). |
backup.images.postgresql |
string | postgres:18.4-alpine |
Image for PostgreSQL backup (pg_dump). |
backup.images.mysql |
string | mysql:8.4 |
Image for MySQL backup (mysqldump). |
backup.images.uploader |
string | helmforge/mc:1.0.0 |
Image for S3 upload (MinIO client). |
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.postgresDumpArgs |
string | "" |
Extra arguments for pg_dump. |
backup.database.mysqlDumpArgs |
string | --single-transaction ... |
Extra arguments for mysqldump. |
Service and Ingress
containerPorts.http configures the actual Apache Answer process through the
upstream-supported INSTALL_PORT, SITE_ADDR, and SWAGGER_ADDRESS_PORT
variables. service.port is client-facing and may differ because the Service uses
the named http target.
| Parameter | Type | Default | Description |
|---|---|---|---|
containerPorts.http |
integer | 80 |
Port opened by the Answer process. |
service.type |
string | ClusterIP |
Service type. |
service.port |
integer | 80 |
Client-facing Service port. |
networkPolicy.enabled |
boolean | true |
Restrict ingress to the named HTTP port. |
networkPolicy.extraIngress |
array | [] |
Optional peers allowed to reach Answer. |
ingress.enabled |
boolean | false |
Enable an Ingress resource. |
ingress.ingressClassName |
string | "" |
Ingress class name. |
ingress.annotations |
object | {} |
Ingress annotations. |
ingress.hosts |
array | [] |
Host and path rules. |
ingress.tls |
array | [] |
TLS configuration. |
Example with separate ports:
containerPorts:
http: 8080
service:
port: 9080
Security Defaults
The Answer pod runs as UID/GID 1000 with RuntimeDefault seccomp, a read-only
root filesystem, disabled privilege escalation, and only the
NET_BIND_SERVICE capability required by the default port 80. /data remains
writable and /tmp is ephemeral. Kubernetes API token mounting is disabled by
default because Answer does not use the cluster API.
Before upgrading an existing release, verify that the /data volume is writable
by UID/GID 1000. A volume created by an older root-running deployment can
remain root-owned when its storage driver does not apply fsGroup; perform an
administrator-controlled ownership migration before the upgrade when required.
The default NetworkPolicy controls ingress only. With
networkPolicy.extraIngress: [], any source may use the named HTTP port; provide
namespace or pod selectors to restrict access to the ingress controller or
Gateway. NetworkPolicies are additive, so verify that the cluster network plugin
enforces them and that no other policy selecting the Answer pod permits broader
sources. Egress remains unrestricted for SMTP, external databases, identity
providers, plugins, and external content.
Probes
All probes use /healthz as the HTTP path.
| Parameter | Type | Default | Description |
|---|---|---|---|
startupProbe.enabled |
boolean | true |
Enable startup probe. |
startupProbe.failureThreshold |
integer | 30 |
Startup probe failure threshold. |
livenessProbe.enabled |
boolean | true |
Enable liveness probe. |
readinessProbe.enabled |
boolean | true |
Enable readiness probe. |
Resources and Scheduling
| Parameter | Type | Default | Description |
|---|---|---|---|
resources |
object | see values | CPU and memory requests/limits. |
terminationGracePeriodSeconds |
integer | 30 |
Termination grace period. |
serviceAccount.automountServiceAccountToken |
boolean | false |
Mount Kubernetes API credentials. |
nodeSelector |
object | {} |
Node selector for scheduling. |
tolerations |
array | [] |
Tolerations for scheduling. |
extraVolumes |
array | [] |
Extra volumes. |
extraVolumeMounts |
array | [] |
Extra volume mounts. |
extraManifests |
array | [] |
Extra Kubernetes manifests. |