oxid/ docs

// daemon

Environment variables

All configuration is environment-based — no config file for the daemon itself.

VariableDefaultDescription
OXID_DATA_DIR/dataState directory: audit.sqlite, git-cache/, secret.key.
OXID_ADDR0.0.0.0:8080Bind address.
OXID_GC_INTERVAL_SECS30Scheduler tick for GC / deploy-queue retries.
OXID_MASTER_KEYgenerated64-hex master key for secret encryption; generated + persisted to secret.key (0600) when unset.
OXID_API_TOKENunsetBearer token required on /api/v1/*. Unset = open API (fine on localhost) — or set OXID_AUTO_TOKEN=1 to auto-generate and persist it (printed once to logs).
OXID_ALLOW_OPEN_API01 to let a non-loopback daemon start without OXID_API_TOKEN (explicit opt-in to an unauthenticated API).
OXID_AUTO_TOKEN01 for zero-config starts: any of OXID_API_TOKEN / OXID_WEBHOOK_SECRET not set is generated (64 hex), persisted under {data}/ (0600) and printed once — the shipped docker-compose.yml uses this so docker compose up -d works with no .env.
OXID_WEBHOOK_SECRETunsetHMAC-SHA256 secret for GitHub/GitLab/Gitea/Gogs webhooks; webhooks rejected while unset (or auto-generated with OXID_AUTO_TOKEN=1).
OXID_DOCKER_NETWORKunsetShared network with Traefik: containers get subdomains instead of host ports, and scale-to-zero wake-on-request activates. The wizard's infra step bootstraps this with one click (POST /api/v1/infra/bootstrap).
OXID_DAEMON_URLhttp://oxid-daemon:8080The daemon's own address inside OXID_DOCKER_NETWORK (Traefik forwardAuth labels).
OXID_POSTGRES_URLunsetAdmin connection string for a shared Postgres — per-branch logical databases for projects declaring the dependency.
OXID_REDIS_URLunsetBase URL of a shared Redis; per-branch logical databases.
OXID_REDIS_POOL_SIZE16Leasable Redis logical databases.
OXID_DEFAULT_MEMORY_LIMIT_MB512Fallback container memory limit; 0 disables.
OXID_DEFAULT_CPU_LIMIT_MILLICORES1000Fallback container CPU limit; 0 disables.
OXID_RESERVED_MEMORY_MB1024Host memory reserved before admission control starts queueing deploys; 0 disables queueing.
OXID_TLS_CERT / OXID_TLS_KEYunsetPEM paths — when both are set the daemon serves HTTPS directly.
OXID_ALLOW_RESTORE01 to accept POST /api/v1/backup/restore uploads at all (staged, applied on next restart).
OXID_BACKUP_INTERVAL_SECSunsetWhen set (e.g. 300), periodic VACUUM INTO snapshots to {data}/backups/.
OXID_BACKUP_KEEP7Newest snapshots to keep when backups are enabled.
OXID_RATE_LIMIT_PER_SECOND / _BURSTunsetWhen both are set, token-bucket rate limit on protected routes (per client IP).
OXID_BOOTSTRAP_TOKEN_ACCESSloopbackWho GET /api/v1/setup/token hands the auto-generated master token to, pre-auth: loopback (only the daemon's own host), any, or off. A containerized daemon is always bound to 0.0.0.0 and sees every caller as the bridge gateway, so it cannot tell a private publish from a public one — the operator does. The shipped compose sets any because it publishes on 127.0.0.1; widen that publish and you must remove it.
OXID_DEPLOY_CONCURRENCYper fleetQueued deploys run at once per drain wave. Leave it unset and Oxid derives it from the fleet — four per node that accepts placements, capped at 32 — so registering a node raises throughput without touching configuration. Setting it pins that number instead. Builds spend nearly all their time waiting on Docker, so overlapping them is what stops a burst of pushes finishing one after another. Measured on 15 simultaneous pushes to a single host: 7.1s at 4, 4.2s at 16.
OXID_DB_MAX_CONNECTIONS8SQLite pool size. WAL lets readers and the writer run at once, so the read-heavy paths overlap instead of queueing. Writes still serialize — that is SQLite. See the benchmarks.
OXID_TRAEFIK_HTTP_PORT80Host port the built-in Traefik publishes on. The proxy always listens on 80 inside its container; this only moves where that surfaces, for a host whose 80 is taken.
OXID_ALLOW_INSECURE_NODES01 to let a remote node be registered with no TLS material. A Docker socket over plain TCP is root on that machine for anyone who can route to it — this is the explicit opt-in, following OXID_ALLOW_OPEN_API.
OXID_NODE_STATUS_TIMEOUT_SECS5How long a status query to a fleet node may take before this daemon gives up on it for the decision in hand. A partitioned machine sends no RST, so without a deadline a deploy aimed at a healthy node waits out the kernel — measured at 121 s. Raise it for a high-latency link: a deploy takes 3.1 s at no added RTT, 9.5 s at 200 ms and 17.7 s at 400 ms.
OXID_TRAEFIK_POLL_INTERVAL5sHow often Traefik re-polls /api/v1/traefik/config for the fleet's routers. Not the wake latency — a sleeping branch keeps its router, so the request that wakes it arrives immediately — only the delay before a newly created branch becomes routable.
OXID_LANGsystem localeLanguage for the CLI's own messages (en/es). The daemon answers API errors per-request from Accept-Language instead.
RUST_LOGinfoTracing filter, e.g. oxid_daemon=debug,info.
OXID_LOG_FORMATprettypretty or json (one object per line — use in production).

On startup the daemon reconciles its database against Docker's actual state before serving any request, and drains in-flight requests for up to 10s on SIGTERM/Ctrl+C instead of dying mid-request.

Data layout

Everything lives under OXID_DATA_DIR — back this up (or use oxid backup) and you have the whole system.

/data
├── audit.sqlite      # all state + audit trail (WAL mode)
├── secret.key        # AES-GCM master key, mode 0600
├── api-token         # auto-generated when OXID_AUTO_TOKEN=1 (0600)
├── webhook-secret    # auto-generated when OXID_AUTO_TOKEN=1 (0600)
├── git-cache/        # cached clones, one dir per project
└── backups/          # VACUUM INTO snapshots when OXID_BACKUP_INTERVAL_SECS is set

A restore never touches the live database in place — an upload is staged and applied on the next daemon startup.