Skip to content

Deployment

Run Sluice as a container or as a system service.

Example

Goal: start Sluice with TLS on the admin listener and check that it is ready.

admin:
  enabled: true
  listen: ":9091"
  # The variable SLUICE_ADMIN__TOKEN sets the token.
  tls:
    certFile: "./data/tls/server.crt"
    keyFile: "./data/tls/server.key"

Start it:

# Demo secrets. Do not use these values in production.
export SLUICE_ADMIN__TOKEN="demo-admin-token-change-me"
export SLUICE_AUDIT_GENESIS="deployment-demo-genesis"

exec sluice serve --config server.yaml

Check readiness:

curl -s http://localhost:8080/v1/ready
{
  "status": "ok",
  "version": "<version>",
  "datasources": [
    {
      "name": "shop",
      "type": "sqlite",
      "healthy": true
    }
  ]
}

Call the admin API:

curl -s https://localhost:9091/admin/healthz \
  --cacert data/tls/server.crt \
  -H "Authorization: Bearer demo-admin-token-change-me"
{
  "status": "ok",
  "version": "<version>",
  "config_digest": "<digest>",
  "config_version": 1
}

What happened: the data source is healthy. The admin API answered over TLS, with the token.

Ports and health endpoints

Port Key Serves
8080 rest.listen Queries, health endpoints, approval links.
9091 admin.listen The Admin API and /metrics, with admin.enabled: true.
None mcp.listen MCP over HTTP. Set it for mcp.transport: streamable_http.

Keep the admin port off the public network.

Endpoint Listener Answer
GET /v1/health REST 200 while the process runs.
GET /v1/ready REST 200, or 503 with status degraded while a data source is unhealthy.
GET /v1/version REST The build.
GET /admin/healthz Admin The same status, plus config_version. Needs the token.

The REST endpoints need no login.

curl -s http://localhost:8080/v1/health
{
  "status": "ok",
  "version": "<version>"
}
curl -s http://localhost:8080/v1/version
{
  "version": "<version>",
  "commit": "<commit>",
  "build_time": "<time>",
  "go": "<go-version>",
  "parser_version": "pg_query_go/v6 proto=170007"
}

TLS

rest.tls, mcp.tls and admin.tls take the same keys. The lowest version is TLS 1.2.

Key Meaning
certFile, keyFile Certificate and key as PEM files. Both are required. Sluice does not start if it cannot read them.
clientCA A CA file for mutual TLS. Clients must then present a certificate that this CA signed.

Without a tls block, a listener serves plain HTTP and logs a warning. The example creates a test certificate:

# A self-signed certificate for localhost. In production, use one from your CA.
mkdir -p data/tls
openssl req -x509 -newkey rsa:2048 -nodes -days 30 \
  -subj "/CN=localhost" -addext "subjectAltName=DNS:localhost" \
  -keyout data/tls/server.key -out data/tls/server.crt 2>/dev/null

Container

Tag Content
ghcr.io/bino-bi/sluice:<version> linux/amd64 and linux/arm64. Use this tag.
<version>-amd64, <version>-arm64 One architecture.
latest Published only by a stable release, never by a pre-release.
  • The tag has no leading v: release v0.1.0-rc.3 is tag 0.1.0-rc.3.
  • The images are signed with cosign. See Install.
  • The image has no shell and runs as user 65532 in /var/lib/sluice. This user must read the mounted files and write the audit log. For the compose file below, run serve.sh once, then sudo chown -R 65532:65532 data in examples/deployment.
  • The image declares 8080, 8081 and 9090. Publish the ports that you configured.
  • docker build -t sluice:dev . builds your own image. SLUICE_IMAGE=sluice:dev makes the example compose files use it.
  • --build-arg LIBDUCKDB_JOBS=8 raises the 4 jobs of its DuckDB compile.
# Runs the released image with the files of this example.
services:
  sluice:
    image: ${SLUICE_IMAGE:-ghcr.io/bino-bi/sluice:0.1.0-rc.3}
    ports:
      - "8080:8080"
      # Only this host can reach the admin API.
      - "127.0.0.1:9091:9091"
    environment:
      SLUICE_ADMIN__TOKEN: "${SLUICE_ADMIN_TOKEN:?set SLUICE_ADMIN_TOKEN}"
      SLUICE_AUDIT_GENESIS: "${SLUICE_AUDIT_GENESIS:?set SLUICE_AUDIT_GENESIS}"
    volumes:
      - ../server.yaml:/etc/sluice/server.yaml:ro
      - ../policies.d:/etc/sluice/policies.d:ro
      # User 65532 must own this directory: it reads the TLS key and writes the audit log.
      - ../data:/etc/sluice/data
    working_dir: /etc/sluice
    command: ["serve", "--config=/etc/sluice/server.yaml"]
    restart: unless-stopped

systemd

# Install as /etc/systemd/system/sluice.service.
[Unit]
Description=Sluice
After=network-online.target
Wants=network-online.target

[Service]
User=sluice
Group=sluice
WorkingDirectory=/var/lib/sluice
# Holds SLUICE_ADMIN__TOKEN and the secret variables. Give it mode 0600.
EnvironmentFile=/etc/sluice/sluice.env
ExecStart=/usr/local/bin/sluice serve --config /etc/sluice/server.yaml
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure

[Install]
WantedBy=multi-user.target

Signals and exit status

Signal Effect
SIGTERM, SIGINT Sluice stops. REST requests get 10 seconds to finish, admin requests 5.
SIGHUP Sluice reloads the policy directory.

Exit status 1 means a start error, 2 a listener error.

DuckDB extensions

Most data source types need a DuckDB extension. Some builds download it to ~/.duckdb/extensions at first use.

Build Included Downloads for
Container images All extensions Nothing
linux_amd64 release, make build-static httpfs, excel, sqlite_scanner postgres, mysql, motherduck
linux_arm64 and darwin releases, make build Nothing Every type except duckdb_file

make build-static first runs make libduckdb, a slow compile. scripts/fetch-duckdb-extensions.sh fills the directory ahead of time.

See also