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:latestMount 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
| Property | Value |
|---|---|
| Image | valontechnologies/gestaltd:latest |
| Platforms | linux/amd64, linux/arm64, linux/arm/v7 |
| Base | Static (scratch with CA certificates) |
| Variants | :latest-alpine (Alpine, has shell) |
| Port | 8080 |
| Entrypoint | /gestaltd |
| Default command | serve --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:latestFor 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.