Skip to main content

Wallabag on GKE Autopilot

Wallabag on GKE Autopilot

Wallabag is a free, open-source, self-hosted "read it later" article archiving app — a Pocket alternative. Save articles from a browser extension, bookmarklet, mobile app, or the REST API, then read them later in a clean, distraction-free view with full-text search, tagging, annotations, and RSS feeds of your saved items. This module deploys Wallabag on GKE Autopilot on top of the App_GKE foundation, which provisions and manages the shared Google Cloud and Kubernetes infrastructure.

This guide focuses on the cloud services Wallabag uses and how to explore and operate them from the Google Cloud Console and the command line. For the mechanics that are common to every GKE application — Workload Identity, ingress, autoscaling, CI/CD, Cloud Armor, IAP, Binary Authorization, VPC Service Controls, backups, and the deployment lifecycle — refer to the App_GKE foundation guide rather than repeating them here.


What Wallabag costs on RAD, and how that compares​

Running Wallabag on GKE Autopilot in your own Google Cloud project costs about US$96.70/month in Google Cloud charges (about US$169.70 if this isn't your only Autopilot cluster), on top of a one-off 110-credit RAD module fee (US$11) — higher than the Cloud Run module's fee for the same reason every GKE variant in this batch costs more to deploy: the pod and cluster overhead replace Cloud Run's request-based pricing. GKE Autopilot suits Wallabag that must run beside other Kubernetes workloads; for the lowest cost, see the Cloud Run guide instead. In a RAD-managed project the module fee drops to 99 credits (10% lower) — see Pause it for free below.

What you pay on RAD​

Own projectRAD-managed project
Module fee (one-off)110 credits99 credits (10% lower)
Build time~3-6 credits per build, either way~3-6 credits per build, either way
Google Cloud running costUS$96.70/month, billed by Google at list price~93 credits/day (about 2790/month, US$279 at the top-up price), metered at list price plus RAD's margin

10 credits cost US$1 on a one-off top-up, and less on a monthly plan.

Default running cost in your own project

Resource (module default)Per month
GKE Autopilot pod, 1x vCPU / 2 GiBUS$39.68
Cloud SQL for MySQL, 1 vCPU / 3.75 GB, zonal, 10 GB SSDUS$51.02
Cloud NAT and networkingUS$5
Cloud Storage (add-ons, backups)US$1
TotalUS$96.70

The GKE cluster management fee is excluded from the total above because it is shared across every GKE app in the project — $0 if this is the only one, otherwise roughly $73/month split across however many you run.

How it compares​

  • wallabag's own creators sell official hosting at wallabag.it from about €11/year — verified on wallabag.it, 8 October 2026 — far cheaper than either RAD option here, because it is a shared multi-tenant service for personal use rather than a dedicated project.
  • A self-managed VPS comparison, sized to Wallabag's 1 vCPU / 2 GiB default: a Hetzner CPX22 or DigitalOcean 2 vCPU/4GB Droplet runs about $24/month — plus your own time for MySQL backups and patching, and no Kubernetes control plane to run.
  • As with the Cloud Run variant, the honest comparison against wallabag.it's own hosting is control and isolation, not price — RAD's case is a dedicated project for a team archive or a lab deployment.

Pause it for free: delete a RAD-managed project, restore it when you need it​

This is the headline advantage of a RAD-managed project, and it costs nothing while paused. Deleting a RAD-managed project unlinks its billing first, then asks Google to delete the project outright — Google's own 30-day recoverable soft delete. Unlike deleting one module, this does not tear down Cloud SQL, any VM, or the compute resource one by one: the whole project simply stops, and nothing is charged while it waits, because billing is already unlinked.

Restoring, within 30 days and only by the project's owner, asks Google to undelete the project and reattaches its billing account, then asks you to run Update on each deployment to confirm everything came back. Because nothing was individually destroyed, that Update finds the same resources already there — it is a check, not a rebuild, and an Update never charges the module fee again. This costs only a handful of credits (under US$1) in total for a typical 2-3-deployment chain.

What this needs: you must own the project (not one RAD only manages billing for), you must restore it yourself within 30 days — after that Google deletes it for good — and restoring is admitted like creating a new project, so your purchased credit balance must still clear the tier's floor (100 credits for the sandbox tier most study/demo use fits). Google says most services are fully working again within 36 hours of a restore.

One real gap: nightly backups are written to a bucket inside the project, and that bucket has Cloud Storage's soft-delete explicitly turned off, so it is very likely gone as soon as you delete the project — even though the project itself is recoverable for 30 days. If you've customised Wallabag and want to keep that work, copy a backup out (to Google Drive, or a bucket outside the project) before deleting. For a default install with nothing irreplaceable in it, this does not matter.

Pay only while you use it, the other way: delete and redeploy​

In your own project, or once 30 days have passed on a RAD-managed one, there is no free pause — deleting removes the resources for good, and bringing Wallabag back means redeploying from scratch. That costs about 113-116 credits in your own project, or about 102-105 credits in a RAD-managed one.

Deleting only saves money once Wallabag would otherwise sit unused long enough to clear that redeploy cost against its own running cost — about 4 days or more in your own project (about US$3.22 a day), or about 1 days or more in a RAD-managed one (93 credits a day).

Keep data first: nightly backups go to a bucket inside the deployment and are deleted with it, so copy the latest backup out before deleting if you want to keep it.

Lab sessions and Managed Environments​

Lab sessions, for training: a trainer runs a session for a class. Each participant gets the app in their own Google Cloud project for 15 minutes to 24 hours, within an allowance the trainer sets. Either the trainer funds every place, or each participant pays for their own. Everything is deleted when the session ends and unused credits go back to the trainer.

Managed Environments, for consultancies: a partner runs the app for a client from a ring-fenced wallet it funds, and settles with the client directly. If the wallet runs low, billing pauses and the data is kept, so nobody receives an unexpected charge. At the end the partner hands the project over and the deployments become the client's own.

Credits can be bought in more than 20 currencies, including XAF, XOF, NGN, GHS, KES and ZAR, by card, bank transfer or mobile money.

Sources (8 October 2026): Google Cloud Billing Catalog API list prices; radmodules.dev/pricing for RAD's own fees and daily-credit estimates; Google's project delete/restore documentation for the pause/restore claims; wallabag.it hosting.

1. Overview​

Wallabag runs as a PHP/Symfony pod (nginx + php-fpm under s6-overlay) on GKE Autopilot. The deployment wires together a focused set of Google Cloud services:

CapabilityGoogle Cloud serviceNotes
ComputeGKE AutopilotPHP/Symfony pod, 1 vCPU / 2 GiB by default; single replica by default (GKE has no scale-to-zero)
DatabaseCloud SQL for MySQL 8.0Required — Wallabag_Common fixes the engine; PostgreSQL is not supported
Object storageCloud StorageA generic data bucket is provisioned, but Wallabag does not read or write it — all content lives in MySQL
SecretsSecret ManagerAuto-generated APP_SECRET (Symfony security token); database password
IngressCloud Load BalancingExternal LoadBalancer, custom domain + managed certificate enabled by default

Sensible defaults worth knowing up front:

  • MySQL 8.0 is mandatory. The database engine is fixed by Wallabag_Common; selecting any other engine breaks the deployment.
  • enable_cloudsql_volume = true. A Cloud SQL Auth Proxy sidecar listens on 127.0.0.1:3306; wallabag.tf additionally pins DB_HOST = "127.0.0.1" so the wrapper entrypoint always dials the sidecar. The Cloud Run variant instead connects over the instance private IP.
  • Single Secret Manager secret. APP_SECRET (a Symfony security token) is generated automatically, overriding Wallabag's publicly-known baked-in default. There is no separate generated admin-password secret.
  • min_instance_count = 1, max_instance_count = 1 by default. GKE has no scale-to-zero; keep replicas at 1 unless Wallabag's session/cache behaviour under multiple pods has been verified.
  • service_type = LoadBalancer with session_affinity = "ClientIP"; enable_custom_domain = true and reserve_static_ip = true by default.
  • enable_nfs defaults true but is functionally unused. It mounts Cloud Filestore NFS at /var/lib/wallabag, but Wallabag's image WORKDIR is /var/www/wallabag — nothing writes to the mounted path. Safe to disable.
  • No separate migration job. Wallabag's own bin/console wallabag:install handles both schema creation and first-run setup in one idempotent step.

2. Google Cloud Services & How to Explore Them​

All commands assume you have run gcloud container clusters get-credentials <cluster> --region <region> --project <project> and that PROJECT, REGION, and NAMESPACE are set. The namespace and other identifiers are reported in the deployment Outputs.

A. GKE Autopilot — the Wallabag workload​

Wallabag runs as a single-replica Deployment by default. Autopilot bills for the CPU/memory the pod actually requests.

  • Console: Kubernetes Engine → Workloads → select the Wallabag workload for pods, events, and logs. Kubernetes Engine → Services & Ingress shows the external IP.
  • CLI:
    kubectl get pods,svc -n "$NAMESPACE"
    kubectl logs -n "$NAMESPACE" deploy/<service-name> --tail=100

See App_GKE for how Autopilot, scaling, and the workload type (Deployment vs StatefulSet) are managed.

B. Cloud SQL for MySQL 8.0​

Wallabag stores all application data (saved articles, tags, users, annotations) in a managed Cloud SQL for MySQL 8.0 instance. The pod reaches it privately through a Cloud SQL Auth Proxy sidecar listening on 127.0.0.1:3306. On first deploy, a db-init job creates the application database and user, followed by wallabag-install, which runs Wallabag's own installer to create the schema.

  • Console: SQL → select the instance for connections, backups, flags, and metrics.
  • CLI:
    gcloud sql instances list --project "$PROJECT"
    gcloud sql instances describe <instance-name> --project "$PROJECT"
    gcloud sql connect <instance-name> --user=<db-user> --database=<db-name> --project "$PROJECT"

The instance name, database, user, and the Secret Manager secret holding the password are all in the Outputs. See App_GKE for the connection model, automated backups, and password rotation.

C. Cloud Storage​

A generic data bucket is provisioned by default (via the Foundation's storage_buckets input), but Wallabag itself never reads or writes it — all content lives in MySQL, and gcs_volumes (which would fuse-mount a bucket into the pod) is empty by default.

  • Console: Cloud Storage → Buckets.
  • CLI:
    gcloud storage buckets list --project "$PROJECT"

See App_GKE for CMEK options and GCS Fuse mounts.

D. Secret Manager​

One secret is generated automatically and stored in Secret Manager: APP_SECRET (materialised under that simple key — GKE's SecretSync CRD rejects targetKey values containing __, so the real SYMFONY__ENV__SECRET name is aliased at container start by the wrapper entrypoint rather than being the synced key itself). The database password is managed separately by the foundation.

  • Console: Security → Secret Manager.
  • CLI:
    gcloud secrets list --project "$PROJECT" --filter="name~app-secret"
    gcloud secrets versions access latest --secret=<secret-name> --project "$PROJECT"

See App_GKE for the Secret Store CSI integration and rotation.

E. Networking & ingress​

By default the workload is exposed through an external Cloud Load Balancing IP with a reserved static IP and a Kubernetes Ingress for custom domains.

  • Console: Network services → Load balancing; VPC network → IP addresses.
  • CLI:
    kubectl get ingress,svc -n "$NAMESPACE"
    gcloud compute addresses list --project "$PROJECT"

See App_GKE for custom domains, Cloud CDN, and static IP details.

F. Cloud Logging & Monitoring​

Pod stdout/stderr flow to Cloud Logging; GKE and Cloud SQL metrics flow to Cloud Monitoring, with optional uptime checks and alert policies.

  • Console: Logging → Logs Explorer; Monitoring → Dashboards / Alerting.
  • CLI:
    gcloud logging read 'resource.type="k8s_container" AND resource.labels.namespace_name="'"$NAMESPACE"'"' \
    --project "$PROJECT" --limit 50

3. Wallabag Application Behaviour​

  • Two-stage init chain, not a Laravel-style separate migrate step. db-init (mysql:8.0-debian) creates the empty application database and user and grants privileges. wallabag-install then depends on db-init and reuses the same custom app image (so the wrapper entrypoint's env aliasing still runs) with its command overridden to bin/console wallabag:install --env=prod -n. This single command performs both schema creation and first-run setup (including seeding the default administrator account) — there is no separate migration job to run on upgrades; re-running wallabag:install against an already-installed database is safe and idempotent.
  • Health check behaviour. The startup probe is TCP on port 80 — it only needs nginx to bind, independent of installer progress. The liveness probe is HTTP GET /login, which returns a plain 200. It deliberately avoids /: the root redirects unauthenticated requests to /login (HTTP 302), which Kubernetes would accept but the Gateway's backend health check — mirrored from this probe — does not, since it requires a literal 200.
    EXTERNAL_IP=$(kubectl get svc -n "$NAMESPACE" -o jsonpath='{.items[0].status.loadBalancer.ingress[0].ip}')
    curl -s -o /dev/null -w "%{http_code}\n" "http://${EXTERNAL_IP}/" # expect 302
  • First-run administrator account. wallabag:install --env=prod -n creates Wallabag's own default administrator account using Wallabag's documented installation defaults (username and password both wallabag) — there is no Secret Manager secret holding a generated admin password. Change this password immediately after first login. New accounts cannot self-register (SYMFONY__ENV__FOSUSER_REGISTRATION = "false") — create additional users from the admin UI or with kubectl exec ... -- bin/console fos:user:create.
  • Inspect job execution:
    kubectl get jobs -n "$NAMESPACE"
    kubectl logs -n "$NAMESPACE" job/<db-init-or-wallabag-install-job-name>

4. Configuration Variables​

Variables are grouped exactly as they appear on the deployment platform. Only settings specific to or notable for Wallabag are listed; every other input is inherited from App_GKE with its standard behaviour.

Group 1 — Project & Identity​

VariableDefaultDescription
project_id(required)Target Google Cloud project.
regionus-central1Region for the cluster and regional resources.

Group 3 — Application Identity​

VariableDefaultDescription
application_namewallabagBase name for resources. Do not change after first deploy.
application_versionlatestBase image tag for wallabag/wallabag. "latest" maps to a pinned tag (2.6.14) at build time.

Group 4 — Runtime & Scaling​

VariableDefaultDescription
container_image_sourcecustomBuilds the wrapper image via Cloud Build. "prebuilt" skips the DB/secret-aliasing wrapper entirely.
min_instance_count / max_instance_count1 / 1GKE has no scale-to-zero; single replica by default.
container_port80Wallabag's nginx listens on port 80.
cpu_limit / memory_limit1000m / 2GiPer-pod resource limits.
enable_cloudsql_volumetrueCloud SQL Auth Proxy sidecar on 127.0.0.1. Keep true on GKE.

Group 6 — GKE Backend & Cluster​

VariableDefaultDescription
service_typeLoadBalancerExternal IP by default.
session_affinityClientIPRoutes a client's requests to the same pod.
workload_typenullAuto-resolves to Deployment (Wallabag needs no per-pod PVC identity).

Group 11 — Jobs & Scheduled Tasks​

VariableDefaultDescription
initialization_jobs[]Leave empty to use the built-in db-init → wallabag-install chain.

Group 13 — Filesystem (NFS)​

VariableDefaultDescription
enable_nfsfalseFunctionally unused — mounted at /var/lib/wallabag, but Wallabag's image writes nothing there. Safe to disable.

Group 14 — Cloud Storage & Artifact Registry​

VariableDefaultDescription
storage_buckets[{ name_suffix = "data" }]Generic bucket. Not read or written by Wallabag.
gcs_volumes[]Nothing fuse-mounted into the pod by default.

Group 16 — Database Backend​

VariableDefaultDescription
database_typeresolves to MYSQL_8_0Fixed by Wallabag_Common.
application_database_name / application_database_userwallabagTenant-prefixed at deploy time. Immutable after first deploy.
db_host_env_var_name / db_user_env_var_name / db_name_env_var_name / db_port_env_var_name / db_password_env_var_name"" (all empty)Unused by Wallabag — the wrapper entrypoint reads the standard DB_* vars directly and aliases them onto SYMFONY__ENV__DATABASE_* itself.

Group 19 — Custom Domain, Static IP & Networking​

VariableDefaultDescription
enable_custom_domaintrueKubernetes Ingress with application_domains.
reserve_static_iptrueA stable IP that survives redeploys.
network_tags["nfsserver"]Firewall targeting; required for NFS connectivity when enable_nfs = true.

Group 15 — Redis​

VariableDefaultDescription
enable_redisfalsePurely optional — only used by Wallabag's asynchronous bulk-import feature.

Every other input (Group 0 metadata, Group 2 environment, Group 5 secrets, Group 7 StatefulSet, Group 8 resource quota, Group 9 reliability, Group 10 observability, Group 12 CI/CD, Group 17 backup, Group 18 custom SQL, Group 20 IAP, Group 21 Cloud Armor, Group 22 VPC-SC) behaves exactly as documented in App_GKE.


5. Outputs​

Returned on a successful deployment — the quickest way to locate and explore the running resources.

OutputDescription
service_nameKubernetes Service name.
namespaceKubernetes namespace.
service_cluster_ip / service_external_ipInternal / external IP.
service_urlService URL.
database_instance_nameCloud SQL instance name.
database_name / database_userApplication database name / user.
database_password_secretSecret Manager secret holding the DB password.
database_host127.0.0.1 via the Cloud SQL Auth Proxy sidecar.
storage_bucketsCreated Cloud Storage buckets.
container_image / container_registryDeployed image and Artifact Registry repo.
initialization_jobsNames of the setup jobs (db-init, wallabag-install).
kubernetes_readyWhether the cluster endpoint is available and all K8s resources are deployed.
deployment_id / tenant_id / resource_prefixNaming identifiers.
project_id / project_numberProject identifiers.
vpc_sc_enabled / vpc_sc_perimeter_name / vpc_sc_dry_run_modeVPC-SC status.

6. Configuration Pitfalls & Sensible Defaults​

Risk: Critical (data loss / outage / security) — High (service degraded) — Medium (cost or partial degradation) — Low (minor).

Inherited plan-time validation. This module passes its configuration through the App_GKE foundation engine, which validates values and combinations at plan time. Invalid configuration fails the plan with a clear, named error before any resource is created.

SettingSensible valueRiskConsequence if wrong
DB driver env var (SYMFONY__ENV__DATABASE_DRIVER, hardcoded in entrypoint.sh)must be set explicitly (pdo_mysql here)CriticalWallabag's shipped parameters.yml defaults database_driver to pdo_sqlite. Setting only SYMFONY__ENV__DATABASE_HOST/_PORT/_NAME/_USER/_PASSWORD with no explicit driver var still silently installs against a throwaway local SQLite file — the install "succeeds," the pod reports Ready, but all data lives in an ephemeral file wiped on every pod restart or redeploy, and MySQL is never touched. No error is raised. If this module is ever cloned as a template for another Symfony-based application, verify the DB driver env var is set explicitly — this class of failure is undetectable from the outside; use kubectl exec into the pod and check the boot logs ("Configuring the SQLite database..." vs. a MySQL connection line) to confirm. See App_GKE and App_CloudRun for how the Foundation injects DB env vars generically — the app-specific translation and any missing pieces are always the calling module's responsibility.
application_database_name / application_database_userSet onceCriticalImmutable after first deploy; renaming recreates the DB/user and destroys all saved articles.
APP_SECRET (auto-generated)Never hand-edit in Secret Manager after first bootHighWallabag uses this as a Symfony security-signing key; changing it invalidates CSRF tokens and any signed URLs already issued.
Default administrator credentials (wallabag / wallabag, seeded by wallabag:install)Change immediately after first loginHighThe installer seeds Wallabag's own well-known default credentials — anyone who knows the service URL and the public default can log in until the password is changed.
container_image_sourcecustomCriticalSwitching to prebuilt deploys the stock wallabag/wallabag image with no wrapper entrypoint — the DB and secret env-var aliasing never runs, so the pod cannot reach MySQL at all.
enable_cloudsql_volumetrue on GKECriticalSetting false removes the Auth Proxy sidecar the wrapper entrypoint's DB_HOST = "127.0.0.1" pin depends on — the pod cannot reach Cloud SQL.
max_instance_count1 unless verified otherwiseHighScaling beyond 1 pod without verifying Wallabag's session/cache behaviour risks inconsistent user sessions across pods.
enable_nfsfalse unless needed for another purposeLow / costDefaults true and provisions a Filestore share that Wallabag never uses — a needless recurring cost.
enable_cloud_armorenable for productionMediumThe service is publicly reachable without WAF protection by default.

For the foundation behaviour referenced throughout — Workload Identity, ingress, autoscaling, CI/CD, Cloud Armor, IAP, Binary Authorization, VPC-SC, backups, and image mirroring — see App_GKE. Wallabag-specific application configuration shared with the Cloud Run variant is described in Wallabag_Common.

Need RAD to do something it does not do yet? Request it on the roadmap, or vote on what is already there.