Skip to content

Wallabag

Self-hosted read-it-later application. Wallabag saves articles from the web by extracting their full text and storing them in your own PostgreSQL database. Read your saved articles offline across any device, organize them with tags, annotations, and reading lists, and share them with configurable access controls.

Key Features

  • Full-text extraction — saves the complete article content, not just a link
  • Multi-device access — web UI, mobile apps, browser extensions, and e-reader exports
  • Tag and annotation system — organize articles with tags, highlights, and notes
  • Working first install — a setup init container installs the schema on first start and runs Doctrine migrations on every upgrade
  • Initial super-admin from a Secret — configurable username and email with a generated or supplied password
  • PostgreSQL backend — bundled subchart or external database
  • Optional Redis — subchart or external Redis for the asynchronous import queue
  • Persistent storage — PVC-backed data directory for downloaded article images
  • S3 backup — scheduled pg_dump of the Wallabag database to S3-compatible storage
  • Ingress and Gateway API — Ingress with TLS or Gateway API HTTPRoutes
  • Hardened defaults — no privilege escalation, minimal capabilities, RuntimeDefault seccomp, no ServiceAccount token

Installation

HTTPS repository:

helm repo add helmforge https://repo.helmforge.dev
helm repo update
helm install wallabag helmforge/wallabag

OCI registry:

helm install wallabag oci://ghcr.io/helmforgedev/helm/wallabag

Getting Started

The first pod start installs the schema and creates the initial super-admin. Read its password and open the UI:

kubectl rollout status deploy/wallabag-wallabag --timeout=300s
kubectl get secret wallabag-wallabag-admin -o jsonpath='{.data.admin-password}' | base64 -d; echo
kubectl port-forward svc/wallabag-wallabag 8080:80

Log in at http://localhost:8080/ as wallabag (or wallabag.admin.username). Resource names follow <release>-wallabag.

Deployment Examples

# values.yaml — Wallabag with bundled PostgreSQL (default)
wallabag:
  domainName: 'https://wallabag.example.com'
  secret: 'a-random-symfony-app-secret-here'

postgresql:
  enabled: true
  auth:
    password: 'postgres-password'

persistence:
  enabled: true
  size: 5Gi

ingress:
  enabled: true
  ingressClassName: traefik
  hosts:
    - host: wallabag.example.com
      paths:
        - path: /
          pathType: Prefix
# values.yaml — Wallabag with PostgreSQL and Redis for the asynchronous import queue
wallabag:
  domainName: 'https://wallabag.example.com'
  secret: 'a-random-symfony-app-secret-here'

postgresql:
  enabled: true
  auth:
    password: 'postgres-password'

redis:
  enabled: true
  auth:
    password: 'redis-password'

persistence:
  enabled: true
  size: 5Gi

ingress:
  enabled: true
  ingressClassName: traefik
  hosts:
    - host: wallabag.example.com
      paths:
        - path: /
          pathType: Prefix
# values.yaml — Wallabag with external PostgreSQL and Redis
wallabag:
  domainName: 'https://wallabag.example.com'
  secret: 'a-random-symfony-app-secret-here'

postgresql:
  enabled: false

database:
  external:
    host: postgresql.database.svc
    port: '5432'
    name: wallabag
    username: wallabag
    password: 'db-password'

redis:
  enabled: false

externalRedis:
  host: redis.cache.svc
  port: '6379'
  existingSecret: wallabag-redis
  existingSecretPasswordKey: redis-password

persistence:
  enabled: true
  size: 5Gi

ingress:
  enabled: true
  ingressClassName: traefik
  hosts:
    - host: wallabag.example.com
      paths:
        - path: /
          pathType: Prefix
# values.yaml — Daily backup of the Wallabag database to S3
wallabag:
  domainName: 'https://wallabag.example.com'
  secret: 'a-random-symfony-app-secret-here'

postgresql:
  enabled: true
  auth:
    password: 'postgres-password'

backup:
  enabled: true
  schedule: '0 3 * * *'
  s3:
    endpoint: https://s3.amazonaws.com
    bucket: my-wallabag-backups
    accessKey: '<set-me>'
    secretKey: '<set-me>'

persistence:
  enabled: true
  size: 5Gi
# values.yaml — Wallabag published through a shared Gateway
wallabag:
  domainName: 'https://wallabag.example.com'

gatewayAPI:
  enabled: true
  httpRoutes:
    - name: public
      parentRefs:
        - name: shared-gateway
          namespace: gateway-system
          sectionName: https
      hostnames:
        - wallabag.example.com

Configuration Reference

Core

Parameter Type Default Description
global object {} Global values shared with parents.
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/wallabag/wallabag Wallabag container image.
image.tag string "2.6.14" Image tag.
image.pullPolicy string IfNotPresent Image pull policy.
imagePullSecrets array [] Pull secrets for private registries.

Wallabag Configuration

Parameter Type Default Description
wallabag.port integer 80 nginx listener inside the container; the process binds this port.
wallabag.domainName string "https://wallabag.example.com" Public URL of the Wallabag instance. Must be changed before deploying.
wallabag.serverName string Wallabag Instance name shown in the UI and API.
wallabag.secret string "" Symfony secret; generated once and preserved across upgrades.
wallabag.existingSecret string "" Existing Kubernetes Secret containing the Symfony secret.
wallabag.existingSecretKey string symfony-secret Key inside the existing secret holding the secret value.
wallabag.registration boolean false Allow new user registrations. Disabled by default for security.
wallabag.admin.username string wallabag Initial super-admin username, applied on the first install.
wallabag.admin.email string [email protected] Initial super-admin email, applied on the first install.
wallabag.admin.password string "" Initial super-admin password; generated once when empty.
wallabag.admin.existingSecret string "" Existing Secret with the initial super-admin password.
wallabag.admin.existingSecretPasswordKey string admin-password Key inside the admin Secret.
wallabag.extraEnv array [] Extra environment variables injected into the Wallabag container.

Database Setup

Parameter Type Default Description
setup.enabled boolean true Install the schema on first start and run migrations on each start.
setup.databaseWaitTimeoutSeconds integer 300 Seconds the setup container waits for PostgreSQL.
setup.resources object {} Resources for the setup init container.
Change domainName before deploying

The default wallabag.domainName is https://wallabag.example.com. Wallabag uses this value to generate internal links for article sharing, e-reader exports, and API responses. Deploy with your actual public URL or internal links will be broken.

Use existing Secrets with GitOps

When wallabag.secret and wallabag.admin.password are empty, the chart generates them once and reads them back from the cluster on helm upgrade. Tools that render without cluster access, such as Argo CD or helm template, generate new values on every render. Set wallabag.existingSecret and wallabag.admin.existingSecret in that case.

Initial super-admin and registration

The upstream installer always creates wallabag/wallabag. The chart replaces it on the first install with wallabag.admin.username, wallabag.admin.email, and the password from the admin Secret. Later password changes go through the Wallabag UI. wallabag.registration: false keeps public sign-ups disabled.

Database — Embedded Subchart

Parameter Type Default Description
postgresql.enabled boolean true Deploy a bundled PostgreSQL subchart for Wallabag.
postgresql.architecture string standalone PostgreSQL deployment architecture.
postgresql.auth.database string wallabag Database name created by the subchart.
postgresql.auth.username string wallabag Database username created by the subchart.
postgresql.auth.password string "" Database password (auto-generated if empty).

Database — External

Parameter Type Default Description
database.external.host string "" External PostgreSQL hostname or IP.
database.external.port string "5432" External PostgreSQL port.
database.external.name string wallabag Database name on the external server.
database.external.username string wallabag Username for the external database.
database.external.password string "" Password for the external database (plain text — prefer secret).
database.external.existingSecret string "" Existing secret containing the database password.
database.external.existingSecretPasswordKey string password Key inside the existing secret for the password.

Import Queue — Redis Subchart

Parameter Type Default Description
redis.enabled boolean false Deploy a bundled Redis subchart for the import queue.
redis.architecture string standalone Redis deployment architecture.
redis.auth.password string "" Redis password (auto-generated by the subchart if empty).

Import Queue — External Redis

Parameter Type Default Description
externalRedis.host string "" External Redis hostname or IP.
externalRedis.port string "6379" External Redis port.
externalRedis.password string "" External Redis password (prefer an existing Secret).
externalRedis.existingSecret string "" Existing Secret with the external Redis password.
externalRedis.existingSecretPasswordKey string redis-password Key inside the Redis Secret.
Redis needs import workers

Wallabag uses Redis only as the queue for asynchronous imports. The queue is consumed by wallabag:import:redis-worker processes, which the chart does not run. Keep the internal setting “Enable Redis to fetch data asynchronously” disabled unless you deploy those workers.

Persistence

Parameter Type Default Description
persistence.enabled boolean true Enable a PVC for /var/www/wallabag/data.
persistence.size string 2Gi PVC size.
persistence.storageClass string "" StorageClass for the PVC.
persistence.accessModes array ["ReadWriteOnce"] PVC access modes.
persistence.existingClaim string "" Use an existing PVC instead of creating one.

Service

Parameter Type Default Description
service.type string ClusterIP Kubernetes service type.
service.port integer 80 Service port exposed to the cluster.
service.annotations object {} Annotations for the Service.

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 (e.g. cert-manager).
ingress.hosts array [] Ingress host and path rules.
ingress.tls array [] TLS configuration (secret name and hosts).

Gateway API

Parameter Type Default Description
gatewayAPI.enabled boolean false Render Gateway API HTTPRoutes.
gatewayAPI.httpRoutes array [] HTTPRoutes; each requires parentRefs and defaults to the Service.

Probes

Parameter Type Default Description
probes.path string /api/info HTTP path used by every probe.
probes.startup.enabled boolean true Enable startup probe.
probes.startup.initialDelaySeconds integer 15 Startup probe initial delay.
probes.startup.periodSeconds integer 10 Startup probe period.
probes.startup.timeoutSeconds integer 5 Startup probe timeout.
probes.startup.failureThreshold integer 30 Startup probe failure threshold.
probes.liveness.enabled boolean true Enable liveness probe.
probes.liveness.initialDelaySeconds integer 0 Liveness probe initial delay.
probes.liveness.periodSeconds integer 15 Liveness probe period.
probes.liveness.timeoutSeconds integer 5 Liveness probe timeout.
probes.liveness.failureThreshold integer 3 Liveness probe failure threshold.
probes.readiness.enabled boolean true Enable readiness probe.
probes.readiness.initialDelaySeconds integer 0 Readiness probe initial delay.
probes.readiness.periodSeconds integer 10 Readiness probe period.
probes.readiness.timeoutSeconds integer 5 Readiness probe timeout.
probes.readiness.failureThreshold integer 3 Readiness probe failure threshold.

Backup

The backup CronJob runs pg_dump against the Wallabag PostgreSQL database and uploads the archive to S3. This protects all saved articles, tags, annotations, and user accounts.

Parameter Type Default Description
backup.enabled boolean false Enable scheduled S3 backup CronJob.
backup.schedule string "0 3 * * *" Cron schedule for backups.
backup.suspend boolean false Suspend the CronJob without deleting it.
backup.concurrencyPolicy string Forbid CronJob concurrency policy.
backup.successfulJobsHistoryLimit integer 3 Number of successful Job records to keep.
backup.failedJobsHistoryLimit integer 3 Number of failed Job records to keep.
backup.backoffLimit integer 1 Job retry limit.
backup.archivePrefix string wallabag Prefix for backup archive filenames.
backup.images.postgresql string docker.io/library/postgres:18-alpine Image used for pg_dump.
backup.images.uploader string docker.io/helmforge/mc:1.0.0 Image used for S3 upload.
backup.resources object {} Resources for backup containers.
backup.s3.endpoint string "" S3-compatible endpoint URL.
backup.s3.bucket string "" Target bucket name.
backup.s3.prefix string wallabag Key prefix within the bucket.
backup.s3.createBucketIfNotExists boolean true Create the bucket automatically if it does not exist.
backup.s3.existingSecret string "" Existing secret containing S3 access and secret keys.
backup.s3.existingSecretAccessKeyKey string access-key Key in the existing secret for the S3 access key.
backup.s3.existingSecretSecretKeyKey string secret-key Key in the existing secret for the S3 secret key.
backup.s3.accessKey string "" Inline S3 access key (ignored when existingSecret is set).
backup.s3.secretKey string "" Inline S3 secret key (ignored when existingSecret is set).
backup.database.host string "" Override database host for backup (uses app credentials if empty).
backup.database.port string "" Override database port for backup.
backup.database.name string "" Override database name for backup.
backup.database.username string "" Override database username for backup.
backup.database.password string "" Override database password for backup.
backup.database.existingSecret string "" Existing secret containing backup database credentials.
backup.database.existingSecretPasswordKey string password Key in the existing secret for the backup database password.
backup.database.postgresDumpArgs string "" Extra arguments passed to pg_dump.

Resources and Security

Parameter Type Default Description
resources object {} CPU and memory requests and limits.
podSecurityContext object fsGroup: 65534, RuntimeDefault seccomp Pod-level security context.
securityContext object no privilege escalation, minimal capabilities Security context for the Wallabag and setup containers.
automountServiceAccountToken boolean false Mount the ServiceAccount token into the pod.
Why the container starts as root

The upstream image renders its configuration and starts nginx as root, then runs PHP-FPM as nobody. runAsNonRoot and a read-only root filesystem are therefore not supported. The defaults drop every capability except CHOWN, DAC_OVERRIDE, FOWNER, SETGID, SETUID, and NET_BIND_SERVICE.

Service Account

Parameter Type Default Description
serviceAccount.create boolean false Create a dedicated ServiceAccount.
serviceAccount.name string "" Override the ServiceAccount name.
serviceAccount.annotations object {} Annotations for the ServiceAccount.

Scheduling

Parameter Type Default Description
nodeSelector object {} Node selector for scheduling.
tolerations array [] Tolerations for scheduling.
affinity object {} Affinity rules.
topologySpreadConstraints array [] Topology spread constraints.
priorityClassName string "" PriorityClass for the pod.
terminationGracePeriodSeconds integer 30 Termination grace period.
podLabels object {} Extra labels for the pod.
podAnnotations object {} Extra annotations for the pod.

Extra

Parameter Type Default Description
extraVolumes array [] Extra volumes to attach to the pod.
extraVolumeMounts array [] Extra volume mounts for the container.
extraManifests array [] Extra Kubernetes manifests deployed alongside the chart.

Common Issues

Articles not loading — domainName mismatch

If articles fail to load or share links are broken, verify that wallabag.domainName matches the exact public URL you are using to access Wallabag, including the https:// prefix and without a trailing slash. A mismatch causes Wallabag to generate internal links pointing to the wrong host.

HTTP 500 on every page

Wallabag returns HTTP 500 when its schema is missing. Check kubectl logs deploy/<release>-wallabag -c setup and confirm that setup.enabled is true and PostgreSQL is reachable with the configured credentials.

Restoring a backup

The setup container creates the schema on first start. Restore a pg_dump archive into an empty public schema while Wallabag is scaled to zero; on the next start the setup container only applies newer migrations.

More Information