Tay has two deployment modes. Elixir applications continue to install the Hex package and supervise Tay.child_spec/1. Non-Elixir applications may run the official OCI image and connect a language client via HTTP/JSON or the local Executor Protocol v1 Unix socket. The standalone runtime is only a small OTP host around the same Engine, scheduler, storage implementation, and recovery rules as the library.

Python producer/worker -> HTTPS :8080 ─┐
Python local worker   -> Unix socket ──┴─> Tay OTP release -> /var/lib/tay

Configuration

The image runs as UID/GID 10001:10001 and accepts:

VariableDefaultMeaning
TAY_DATA_DIR/var/lib/tayAuthoritative durable Store root.
TAY_SOCKET_PATH/run/tay/tay.sockExecutor Protocol v1 UDS path; set off for HTTP-only operation.
TAY_HTTP_PORTunsetEnables the Bandit HTTP/JSON API for producers and remote workers.
TAY_HTTP_IP127.0.0.1Listener IP; non-loopback requires mTLS.
TAY_HTTP_TLS_{CERTFILE,KEYFILE,CACERTFILE}unsetAbsolute PEM paths for server certificate, private key and client CA. Set all three together.
TAY_INITIALIZE_IF_MISSINGfalsetrue, TRUE, or 1 permits initialization only when the Store root is genuinely absent.
TAY_DEFAULT_QUEUE_CONCURRENCY10Maximum concurrently executing jobs in the standalone default queue (1..65536). Actual execution is also bounded by registered worker capacity.
TAY_CALLER_TIMEOUT_MS5000Maximum time an admitted Engine call may wait for its reply. A timeout after submission is reported as unknown_outcome; reconcile with the same job ID.
TAY_GROUP_COMMIT_INTERVAL_MS0Durable enqueue group-commit window in milliseconds (0..1000). 0 preserves immediate fsync behavior. Invalid values stop startup.
TAY_CHECKPOINT_INTERVAL_MS60000Periodic crash-safe Store-v2 semantic checkpoint interval in milliseconds (0..4294967295). 0 disables it. Automatic checkpoints run after new durable events even with active jobs, capture live state at a durable frontier without replaying old journal segments, and recovery replays the snapshot plus its tail.
TAY_MAX_STATE_BYTES268435456Conservative in-memory byte budget for active jobs. Accepts bytes or an integer with KB, KiB, MB, MiB, GB, or GiB, for example 5GiB.
TAY_TERMINAL_RETENTION24hInitial retention for terminal jobs. Positive durations use m, h, or d, for example 30m, 1h, 24h, or 7d. The Dashboard/API may use zero as a one-shot full terminal-history purge, after which the persisted retention is 1h. After a successful Dashboard compaction, the persisted Dashboard value takes priority on restart.
TAY_ENABLE_DASHBOARDfalseSet to true, TRUE, or 1 to serve the bundled dashboard.
TAY_DASHBOARD_HOSTlocalhostPublic hostname for dashboard URL and LiveView origin checks.
TAY_DASHBOARD_PORT4000Dashboard listener port.
TAY_DASHBOARD_USERNAMEunsetOptional HTTP Basic Auth username; requires password.
TAY_DASHBOARD_PASSWORDunsetOptional HTTP Basic Auth password; requires username.
TAY_DASHBOARD_SECRET_KEY_BASEunsetAt least 64 random bytes; required when dashboard is enabled.

TAY_DURABILITY is intentionally not an environment setting. The standalone runtime pins durability: :sync and validates the filesystem before writing; an unnoticed override to :write would weaken the production durability contract. Embedded Elixir applications can configure their Engines explicitly, subject to Tay's production validation.

Malformed explicit values fail startup. The socket must be absolute, at most 100 bytes, and outside the data directory. The socket is created with mode 0660 so a worker can use a shared group where the deployment platform supports it.

With a positive group-commit interval, concurrent durable enqueue requests are written in arrival order and acknowledged together only after their batch fsync succeeds. It trades up to the configured wait for fewer fsync calls. It does not change recovery, capacity accounting, or failure semantics; a write/fsync error fails the generation without returning success for that batch. Embedded Engines use the :group_commit_interval_ms option. durability: :write remains immediate.

TAY_MAX_STATE_BYTES uses decimal multipliers for KB, MB, and GB, and binary multipliers for KiB, MiB, and GiB. For example, 5GB is 5_000_000_000 bytes while 5GiB is 5_368_709_120 bytes. A value without a suffix is interpreted as bytes for backward compatibility.

Active-job and terminal-history counts are informational and have no configured maximum. TAY_MAX_JOBS and TAY_MAX_TERMINAL_JOBS are legacy variables and are ignored. Active payloads use one TAY_MAX_STATE_BYTES budget; structural complexity is included through the retained job's flat BEAM heap size rather than a separate item limit. The former TAY_MAX_STATE_NODES variable is ignored. Terminal history is removed only by time-based compaction.

Python HTTP producers and workers need only the HTTP(S) address. Set TAY_SOCKET_PATH=off together with TAY_HTTP_PORT to run without a Unix socket. For an HTTP-only Compose deployment use docker compose up tay; the example's worker service demonstrates the optional local socket mode. The example still declares an unused socket volume in HTTP-only mode; neither the Engine nor Python HTTP clients require a socket in that configuration.

The optional HTTP API runs inside the same Engine. Plaintext is permitted only on loopback; non-loopback binds require the three TLS files and verify client certificates. Mount certificates read-only, ensure UID 10001 can read them, and match the server certificate SAN to the client's DNS name or IP. Legacy TAY_GRPC_* settings fail startup rather than silently disabling the old API.

Tay deliberately does not interpret a pre-existing empty directory as a missing Store. Docker creates a named-volume mount root before the process starts, so mount the volume at /var/lib/tay and set TAY_DATA_DIR=/var/lib/tay/store, as in compose.yml. The image creates both directory targets with ownership 10001:10001; an empty Docker named volume inherits that ownership and does not need a root init container. Bind mounts and volume drivers that preserve different ownership must be prepared for UID/GID 10001 by the operator.

The image supports a read-only root filesystem. Its only writable locations are the durable data volume, the optional ephemeral socket volume, and /tmp. It drops no privileges at startup and never runs as root.

Run with Compose

From the repository root:

docker compose up --build

This starts Tay only. To include the example local socket worker, use docker compose --profile local-worker up --build. Compose lists settings needed for this deployment example; the table above lists all supported standalone environment settings.

Set TAY_ENABLE_DASHBOARD=true in the same Compose deployment to enable the dashboard. The HTTP API is opt-in; to expose it outside the container, set TAY_HTTP_PORT=8080, TAY_HTTP_IP=0.0.0.0, and TAY_HTTP_HOST_BIND=0.0.0.0; mount the three TLS files read-only and set their TAY_HTTP_TLS_* paths. Without mTLS the non-loopback bind is rejected. To omit the local worker socket as well, set TAY_SOCKET_PATH=off and start only the tay Compose service.

The worker example installs the stdlib-only Python client and advertises an example.echo.v1 task. Production workers should pin a tay-client version compatible with the image's protocol version.

For a worker on another host, run Tay without a socket and mount a directory containing the server certificate, its key, and the CA that signs client certificates. For example, after creating the taydata volume:

docker run -d --name tay \
  --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=64m,uid=10001,gid=10001,mode=0700 \
  --cap-drop ALL --security-opt no-new-privileges \
  -p 8080:8080 \
  -e TAY_DATA_DIR=/var/lib/tay/store \
  -e TAY_INITIALIZE_IF_MISSING=true \
  -e TAY_SOCKET_PATH=off \
  -e TAY_HTTP_PORT=8080 -e TAY_HTTP_IP=0.0.0.0 \
  -e TAY_HTTP_TLS_CERTFILE=/etc/tay/tls/server.pem \
  -e TAY_HTTP_TLS_KEYFILE=/etc/tay/tls/server.key \
  -e TAY_HTTP_TLS_CACERTFILE=/etc/tay/tls/client-ca.pem \
  --mount type=volume,src=taydata,dst=/var/lib/tay \
  --mount type=bind,src=/absolute/path/to/tls,dst=/etc/tay/tls,readonly \
  ghcr.io/andriisydorenko1904/tay:1.3.1

Create the volume with docker volume create taydata. Replace the TLS source path, ensure UID 10001 can read the mounted files, and provision client certificates separately for workers. On later starts, set TAY_INITIALIZE_IF_MISSING=false. Restrict the published port to the intended worker network. The Python connection example is in the Python client guide.

The health check succeeds only when the Engine reports ready and the selected transport is available (HTTP if the socket is disabled, otherwise the UDS). A live BEAM process alone is not healthy. The image grants startup a 15-minute health-check grace period, matching the default bounded recovery deadline; steady-state failures still use the ordinary 12 attempts at five-second intervals.

Persistence and operations

/var/lib/tay is persistent state. Back it up using Tay's documented storage procedure; copying a live writable directory is not a supported backup. Deleting the data volume deletes durable job history. /run/tay is runtime IPC state only and may be recreated on every deployment.

Tay remains single-node and single-writer. Never attach one data volume to two Tay containers. It provides at-least-once execution, performs full recovery on restart, and fails closed on invalid, partial, corrupt, or unsupported storage. It does not automatically repair or truncate evidence. Use a local Linux filesystem whose durability properties have been validated for the workload.

The standalone image is not distributed Tay and does not run user job code; external worker processes do that over HTTP/JSON or the local UDS protocol.

Optional dashboard

The same tay OCI image bundles the dashboard from the tay package and a minimal Phoenix endpoint. Set TAY_ENABLE_DASHBOARD=true to expose both /run/tay/tay.sock and the web UI at /tay. Application-level authentication is optional. No second container or image is required.

TAY_ENABLE_DASHBOARD=true docker compose up --build

The headless and dashboard-enabled modes use the same data and transport contracts. See the dashboard container guide for its network boundary, optional credentials, HTTP configuration, and Compose example.