Skip to Content
DeployDocker

Docker

Quick start

export GESTALT_ENCRYPTION_KEY="$(openssl rand -hex 32)" docker run --rm \ -p 8080:8080 \ -e GESTALT_ENCRYPTION_KEY="${GESTALT_ENCRYPTION_KEY}" \ -v "$(pwd)/config.yaml:/etc/gestalt/config.yaml:ro" \ -v gestalt-data:/data \ valontechnologies/gestaltd:latest

Mount a config file at /etc/gestalt/config.yaml. The image is not zero-config and will fail to start without one. Generate the encryption key once with openssl rand -hex 32 and reuse that value for the deployment.

Docker Compose

services: gestalt: image: valontechnologies/gestaltd:latest ports: - "8080:8080" volumes: - ./config.yaml:/etc/gestalt/config.yaml:ro - gestalt-data:/data environment: GESTALT_ENCRYPTION_KEY: "${GESTALT_ENCRYPTION_KEY}" volumes: gestalt-data:

Image details

PropertyValue
Imagevalontechnologies/gestaltd:latest
Platformslinux/amd64, linux/arm64, linux/arm/v7
BaseStatic (scratch with CA certificates)
Variants:latest-alpine (Alpine, has shell)
Port8080
Entrypoint/gestaltd
Default commandserve --locked --config /etc/gestalt/config.yaml --artifacts-dir /data
Config path/etc/gestalt/config.yaml
Data directory/data

For the CLI client image, see Client Docker Image.

Production image

For deterministic production deployments, run gestaltd init locally to resolve plugins and write lock state, then bake the result into a derived image:

deploy/ config.yaml gestalt.lock.json .gestaltd/providers/... .gestaltd/ui/...
FROM valontechnologies/gestaltd:latest-alpine COPY deploy/ /app/ CMD ["serve", "--locked", "--config", "/app/config.yaml"]

This produces a self-contained image that starts without fetching or resolving anything at runtime.

See Configuration for what gestalt.lock.json records and why serve --locked depends on it.

Environment variables and secrets

Gestalt expands ${VAR} placeholders in the config before YAML decoding. The image also supports the *_FILE convention: if VAR is not set but VAR_FILE is, ${VAR} resolves to the contents of that file. If neither is set, config loading fails. Use ${VAR:-} when an explicit empty default is intentional. This works well with Docker secrets:

docker run --rm \ -p 8080:8080 \ -v "$(pwd)/config.yaml:/etc/gestalt/config.yaml:ro" \ -v /run/secrets/encryption-key:/run/secrets/encryption-key:ro \ -e GESTALT_ENCRYPTION_KEY_FILE=/run/secrets/encryption-key \ valontechnologies/gestaltd:latest

For production, use a dedicated secret manager with structured secret refs. See Configuration for details.

Health endpoints

GET /health returns 200 when the process is running. GET /ready returns 503 until providers and the datastore are ready, then 200.

SQLite and persistent storage

SQLite works for local development, demos, and single-instance deployments. Store the database on a mounted volume (e.g. /data/gestalt.db) to persist data across container restarts.

For horizontally scaled deployments, use Postgres or MySQL instead.