Vikunja Common — Shared Application Configuration
Vikunja_Common is the shared application layer for Vikunja. It is
not deployed on its own; instead it supplies the Vikunja-specific configuration
that both Vikunja_GKE and
Vikunja_CloudRun build on, so the two platform
variants behave identically where it matters. End users never configure this layer
directly — it has no deployment UI inputs of its own — but understanding what it
provides explains the defaults you see in the platform docs.
For the infrastructure that actually provisions and runs Vikunja, see the platform guides (Vikunja_GKE, Vikunja_CloudRun) and the foundation guides (App_GKE, App_CloudRun, App_Common).
1. What this layer provides
| Area | Provided by Vikunja_Common | Where it surfaces |
|---|---|---|
| Cryptographic secret | Generates VIKUNJA_SERVICE_JWTSECRET (32-char random) and stores it in Secret Manager | Injected automatically; retrieve via Secret Manager (see below) |
| Container image | Wraps the scratch-based vikunja/vikunja image with a grafted busybox and a custom entrypoint; builds via Cloud Build | container_image output of the platform deployment |
| Database engine | Fixes Cloud SQL for PostgreSQL 15 as the only supported engine | §Database in the platform guides |
| Database bootstrap | Defines the first-deploy job (db-init) that creates the database, user, and grants | initialization_jobs output |
| Runtime DB wiring | Maps the platform DB_* vars onto Vikunja's VIKUNJA_DATABASE_* config and chooses the right connection host/SSL mode | Application behaviour in the platform guides |
| Port & health checks | Sets the container port to 3456 and the default startup/liveness probe targeting /health | §Observability in the platform guides |
2. Cryptographic secret in Secret Manager
One secret is generated automatically and stored in Secret Manager — it is never set in plain text and must never be changed after the first deployment:
VIKUNJA_SERVICE_JWTSECRET— a 32-character random string. Vikunja signs all user session JWTs with it. If it were left unset, Vikunja randomises it on every container start, which invalidates all sessions and logs every user out on each restart.Vikunja_Commontherefore provisions a stable secret. Rotating it after first boot immediately invalidates all active sessions, forcing every user to log in again.
Retrieve the secret after deployment:
# List secrets for this deployment (names include the resource prefix):
gcloud secrets list --project "$PROJECT" --filter="name~jwt-secret"
# Read a secret version:
gcloud secrets versions access latest --secret=<secret-name> --project "$PROJECT"
The database password is generated and managed separately by the foundation; its
secret name is reported in the platform deployment outputs (database_password_secret).
See App_Common for the shared secret and Workload Identity model.
3. Database engine and bootstrap
Vikunja requires PostgreSQL 15; the engine is fixed and MySQL or other
engines are not supported. On the first deployment a one-shot job (db-init) runs
using postgres:15-alpine and idempotently:
- Detects the Cloud SQL Auth Proxy Unix socket and symlinks it into
/tmpforpsqlaccess, - Waits for PostgreSQL to be reachable (up to 60 retries),
- Creates (or updates the password of) the application role,
- Creates the application database with that role as owner (if it does not exist),
- Grants full privileges on the database to the role,
- Signals the Cloud SQL Auth Proxy to shut down gracefully (
/quitquitquit).
Vikunja itself runs its schema migrations on the first application startup — the
db-init job only provisions the empty database and role. The job is safe to
re-run. Inspect the database directly with:
gcloud sql connect <instance-name> --user=<db-user> --database=<db-name> --project "$PROJECT"
The instance, database, and user names are in the platform deployment outputs.
4. Container image and entrypoint
The upstream vikunja/vikunja image is scratch-based: it contains only the
static /app/vikunja/vikunja binary, with no shell and no /etc/passwd. To run a
shell entrypoint that maps the platform's environment onto Vikunja's native config,
the custom Dockerfile is multi-stage:
- Grafts in a static busybox (
COPY --from=busybox /bin/busybox) and usesbusybox shas the entrypoint interpreter — a plainFROM … + RUNthin-build is impossible because there is no shell or user database to resolve. - Pre-creates the attachments dir
/app/vikunja/filesowned by uid 1000 (COPY --chown=1000:0), because the app runs as uid 1000 under a root-owned WORKDIR and cannotmkdirit at boot. For durable attachments, mount NFS over this path. - Builds with an app-specific
VIKUNJA_VERSIONbuild ARG — not the genericAPP_VERSION, which the foundation injects intobuild_argsand would otherwise win the merge and resolvevikunja:latest(a non-existent tag)."latest"maps to a pinned recent release (2.3.0).
The entrypoint script (vikunja-entrypoint.sh) runs before the Go server starts:
- Maps
DB_*toVIKUNJA_DATABASE_*— translates the platform-injectedDB_USER,DB_PASSWORD,DB_NAMEontoVIKUNJA_DATABASE_USER,VIKUNJA_DATABASE_PASSWORD,VIKUNJA_DATABASE_DATABASE, and setsVIKUNJA_DATABASE_TYPE = postgres. Discrete env values need no URL-encoding — Vikunja encodes them itself. - Connects over the private IP, not the socket. Vikunja builds a
postgres://URL internally, so it cannot use the Cloud SQL socket directory as the host — the path/cloudsql/<project>:<region>:<instance>contains colons the URL builder parses as a bogus port (invalid port after host). The entrypoint connects over the Cloud SQL private IP (DB_IP) withsslmode=requireon Cloud Run (Cloud SQL rejects unencrypted private-IP TCP), and over the proxy loopback (127.0.0.1) withsslmode=disableon GKE. It branches on whether the resolved host is loopback — not on whetherDB_IPis set — becauseDB_IPis127.0.0.1on GKE. - Sets
VIKUNJA_SERVICE_PUBLICURLfromCLOUDRUN_SERVICE_URL(Cloud Run) orGKE_SERVICE_URL(GKE) so links and the frontend use the real service address. - Launches the server with
exec /app/vikunja/vikunjaas PID 1.
5. Core application settings
Vikunja_Common establishes the baseline Vikunja environment so the application
comes up correctly on first boot:
- Port — Vikunja listens on
3456. - Database type —
VIKUNJA_DATABASE_TYPE = postgres. - Public URL — derived at runtime from the platform-injected service URL.
- JWT secret —
VIKUNJA_SERVICE_JWTSECRETinjected from Secret Manager. - First-run ownership — Vikunja ships no pre-seeded admin; the first
registered account becomes the owner. After creating it, disable open
registration by setting
VIKUNJA_SERVICE_ENABLEREGISTRATION = "false"viaenvironment_variables.
Platform-specific adjustments handled here:
- Cloud Run connects to Cloud SQL over the private IP (
sslmode=require) and reads its public URL fromCLOUDRUN_SERVICE_URL. - GKE connects over the cloud-sql-proxy sidecar loopback (
sslmode=disable) and reads its public URL fromGKE_SERVICE_URL.
6. Health probe behaviour
The default startup and liveness probes target /health — Vikunja's public,
unauthenticated liveness endpoint that returns 200 as soon as the server binds its
port. A generous startup window accommodates the schema migrations Vikunja runs on
first boot.
- Cloud Run and GKE both use HTTP probes on
/healthwith a 30-second initial delay; the startup probe allows a wide retry window (default failure threshold 30) for first-boot migrations.
7. Object storage
Vikunja stores its data in PostgreSQL and its file attachments on the local
filesystem (/app/vikunja/files). Vikunja_Common therefore declares no
dedicated Cloud Storage bucket (storage_buckets = []). For durable attachments,
enable NFS in the platform variant and mount it over the attachments path.
For the Vikunja-specific, user-facing configuration (variables by group, outputs, and how to explore each service from the Console and CLI), see the platform guides: Vikunja_GKE and Vikunja_CloudRun.