-
Notifications
You must be signed in to change notification settings - Fork 0
Configuration
skulid is configured entirely via environment variables. The
canonical example is .env.example
in the repo root.
| Variable | Meaning |
|---|---|
EXTERNAL_URL |
Public HTTPS URL the daemon answers on (no trailing slash) |
GOOGLE_CLIENT_ID |
OAuth client ID from Google Cloud Console |
GOOGLE_CLIENT_SECRET |
OAuth client secret |
SESSION_SECRET |
Random string used to sign session cookies (≥32 bytes) |
ENCRYPTION_KEY |
Base64 of 32 random bytes; AES-256-GCM key for token storage |
DATABASE_URL |
Postgres DSN (postgres://user:pass@host:5432/db?sslmode=disable) |
Generate the secrets:
openssl rand -base64 48 # SESSION_SECRET
openssl rand -base64 32 # ENCRYPTION_KEY| Variable | Default | Meaning |
|---|---|---|
LISTEN_ADDR |
:8567 |
TCP address the HTTP server binds to |
ANTHROPIC_API_KEY |
unset (off) | Enable the AI assistant; see AI Assistant |
ANTHROPIC_MODEL |
claude-opus-4-7 |
Model the assistant uses |
AUDIT_RETENTION_DAYS |
90 |
How long audit_log rows are kept. 0 disables pruning and lets the table grow forever. |
Every variable in the Required table above is also checked by content, not just presence. The daemon exits at startup if any of these hold:
| Variable | Rejected when |
|---|---|
SESSION_SECRET |
equal to the dev-only-… compose placeholder, or shorter than 32 bytes |
ENCRYPTION_KEY |
decodes to 32 zero bytes (the compose placeholder) |
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET
|
literally dev
|
EXTERNAL_URL |
not https://
|
This exists because docker-compose.yml supplies a default for every one of
them, so a .env that is missing, misnamed, or sitting in the wrong directory
does not produce an error — it produces a running instance whose refresh
tokens are sealed with a key published in this repository and whose session
cookies are signed with a secret anyone can read. Presence checks cannot catch
that. Content checks can.
The startup error names each offending variable and what to do about it.
SKULID_ALLOW_INSECURE_CONFIG=1 downgrades the refusal to a startup WARN
plus a red banner on every page, including the login page. It exists for local
smoke testing without a .env. Never set it in production — an instance
running this way cannot protect the tokens it holds.
These exist for UI mockup work without doing a real Google OAuth
round-trip. Never set in production — the daemon logs a WARN
at startup and every page renders a yellow DEV AUTH BYPASS banner
when on, but it's still your responsibility to keep them out of
prod env files.
| Variable | Default | Meaning |
|---|---|---|
SKULID_DEV_AUTH_BYPASS |
unset (off) | Truthy (1/true/yes/on) registers GET /dev/login; hitting that route claims TOFU as SKULID_DEV_USER_EMAIL and issues a real session. |
SKULID_DEV_USER_EMAIL |
dev@local |
Email recorded as the synthetic owner. |
In docker-compose.yml, set them in the app service environment: block (or a .env file at the repo root that compose reads automatically). They're just regular env vars — no special wiring beyond the daemon checking for them at startup.
services:
app:
environment:
SKULID_DEV_AUTH_BYPASS: "1"
SKULID_DEV_USER_EMAIL: "dev@local"After enabling, hit https://your.host/dev/login (or the "Skip OAuth →" link on the login page) to land on the dashboard.
See Development → Local setup for the longer story on what this does and doesn't bypass.
The bundled docker-compose.yml reads:
| Variable | Default | Used by |
|---|---|---|
POSTGRES_USER |
skulid |
Postgres init + DATABASE_URL
|
POSTGRES_PASSWORD |
changeme |
Postgres init + DATABASE_URL
|
POSTGRES_DB |
skulid |
Postgres init + DATABASE_URL
|
HOST_PORT |
8567 |
Host port mapped to container 8567 |
SKULID_IMAGE |
ghcr.io/ryakel/skulid |
Image repo to pull (override for an internal registry) |
SKULID_TAG |
latest |
Image tag (pin to vX.Y.Z for a specific release) |
-
EXTERNAL_URLis what gets sent to Google as the OAuth redirect URI base and the watch-channel webhook address. If you change it later, re-register webhooks via Settings → Re-register all webhooks (why). -
ENCRYPTION_KEYis the only thing that decrypts your stored refresh tokens. If you lose it, you must reconnect every account. Back it up offline. -
SESSION_SECRETrotation invalidates every active session — users get bounced to the login page, which is fine.