Sign inSign up

tessaryai/tessary:compose

Machine learning & AI
Developer tools
Monitoring & observability
Manifest digest

sha256:407e5be499d253155ab56fdec072e8295869f80aba1cc610e32e02161966e903

Last pushed

2 days by tessaryai

Type

Compose

Manifest digest

sha256:407e5be499d253155ab56fdec072e8295869f80aba1cc610e32e02161966e903

Compose file content

# Production stack: postgres, backend, frontend (Caddy, TLS on :443 when SITE_DOMAIN is set, HTTP on
# :8000 always) and the sandbox-runner launcher. Every service starts on a default `up` except
# `redpanda`, the opt-in ingest buffer under `--profile kafka`.
#
# Usage: fill .env (.env.example lists every key), then `docker compose pull && docker compose up -d`.
# Set TESSARY_VERSION to pin every image; each *_IMAGE key overrides one.
#
# Also the published install: `docker compose -f oci://docker.io/tessaryai/tessary:compose up -d -y`.
# The release stamps the floating TESSARY_VERSION defaults with its version; no version literal lives
# here (scripts/check-version-consistency.sh).
#
# A remote install has no repository to resolve against, so (scripts/check-compose-artifact.sh):
# 1. No host-path bind in a default-profile service; use named volumes.
# 2. Long-syntax `ports:` and literal memory limits: publish decodes without interpolation.
# 3. `env_file:` is inert remotely, so read keys through ${VAR}. Never publish `--with-env`.
# 4. A profile-gated service uses image, volumes and env only.

# Pinned so the stack is identical from any directory, including git worktrees.
name: tessary

services:
  postgres:
    # Postgres 16 with pgvector preinstalled; same data directory format as postgres:16-alpine.
    image: pgvector/pgvector:pg16
    environment:
      POSTGRES_DB: ${POSTGRES_DB:-tessary}
      # Defaulted, not `:?`: Compose refuses to render an unset `:?` key. No host port is published, so the
      # credential is reachable only from the `tessary` network; change it if you publish the port.
      POSTGRES_USER: ${POSTGRES_USER:-tessary}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-tessary}
    volumes:
      # A named volume by default (rule 1). POSTGRES_DATA_DIR still takes a host path (/ or ./), such as an
      # EBS mount. `docker compose down` keeps the volume; `down -v` deletes the data.
      - ${POSTGRES_DATA_DIR:-tessary-postgres-data}:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
      interval: 5s
      timeout: 3s
      retries: 30
      start_period: 10s
    restart: unless-stopped
    networks:
      - tessary

  backend:
    # Unset means `-latest`, which the release repoints. Before the first release no tag exists; use
    # `docker compose up -d --build`.
    image: ${BACKEND_IMAGE:-tessaryai/tessary:backend-${TESSARY_VERSION:-1.11.1}}
    # A literal ceiling (rule 2), so -XX:MaxRAMPercentage sizes the heap against a stated number. Raise it
    # with an override file (docs/self-hosting/setup.mdx).
    mem_limit: 2g
    expose:
      - "8080"
      # OTLP/gRPC, internal only. Inert unless tessary.ingest.otlp.transport is grpc or both.
      # Publishing 4317 to the host also needs `EXPOSE 4317` in backend/Dockerfile.
      - "4317"
    env_file:
      - path: ./.env
        required: false
    environment:
      TESSARY_JDBC_URL: jdbc:postgresql://postgres:5432/${POSTGRES_DB:-tessary}
      # Repeated, not inherited: an unset ${POSTGRES_USER} would hand the backend an empty credential.
      TESSARY_DB_USERNAME: ${POSTGRES_USER:-tessary}
      TESSARY_DB_PASSWORD: ${POSTGRES_PASSWORD:-tessary}
      SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE:-production}
      # Liquibase phones home to config.liquibase.com unless this key is set; Spring's
      # `spring.liquibase.analytics-enabled` does not reach it.
      LIQUIBASE_ANALYTICS_ENABLED: "false"

      # INSECURE PUBLIC DEFAULTS so the quickstart is one command. Anyone can forge a session cookie or decrypt
      # sealed provider keys on an instance still using them. PlaceholderSecretGuard refuses to start with a
      # real SITE_DOMAIN; replace both with `openssl rand -base64 32`. Each must decode to exactly 32 bytes.
      TESSARY_AUTH_COOKIE_PASSWORD: ${TESSARY_AUTH_COOKIE_PASSWORD:-Q0hBTkdFLU1FLWluc2VjdXJlLWRlZmF1bHQtY29va2k=}
      TESSARY_SECRET_KEY: ${TESSARY_SECRET_KEY:-Q0hBTkdFLU1FLWluc2VjdXJlLWRlZmF1bHQtc2VhbCE=}

      # Puts the instance on a hostname and arms the guard above. PublicOriginGuard derives the sign-in origin
      # and WorkOS callback from it. TLS_MODE and ACME_EMAIL are repeated so an ACME boot without an address is refused.
      SITE_DOMAIN: ${SITE_DOMAIN:-}
      TLS_MODE: ${TLS_MODE:-acme}
      ACME_EMAIL: ${ACME_EMAIL:-}

      # Derived from the published port; the code default assumes :8000. With SITE_DOMAIN set it becomes
      # https://<SITE_DOMAIN>/, and an explicit value on another host refuses the boot.
      TESSARY_AUTH_FRONTEND_URL: ${TESSARY_AUTH_FRONTEND_URL:-http://localhost:${HTTP_PORT:-80}/}
      # Explicit so an agentic run cannot no-op on a missing URL.
      TESSARY_OBSERVER_AGENTIC_LAUNCHER_URL: http://sandbox-runner:8080
      # A shipped placeholder, not blank: the launcher rejects every request when its key is empty.
      # PlaceholderSecretGuard covers the exposure.
      TESSARY_OBSERVER_AGENTIC_LAUNCHER_API_KEY: ${TESSARY_OBSERVER_AGENTIC_LAUNCHER_API_KEY:-Q0hBTkdFLU1FLWluc2VjdXJlLWRlZmF1bHQtbGF1bmNo}
      # Required: the sandboxed agent is RCA's only analysis path.
      TESSARY_RCA_AGENTIC_LAUNCHER_URL: http://sandbox-runner:8080
      # Repeated verbatim: compose interpolates each entry from the host env, so a default in one entry is
      # invisible to the others.
      TESSARY_RCA_AGENTIC_LAUNCHER_API_KEY: ${TESSARY_OBSERVER_AGENTIC_LAUNCHER_API_KEY:-Q0hBTkdFLU1FLWluc2VjdXJlLWRlZmF1bHQtbGF1bmNo}
      # The agents' MCP evidence endpoint. The internal name works while SANDBOX_NETWORK_ISOLATION is off (the
      # default). With isolation on, or SANDBOX_BACKEND=e2b, set it to the public origin; PublicOriginGuard
      # rejects an unreachable value.
      TESSARY_RCA_AGENTIC_MCP_BASE_URL: ${TESSARY_RCA_AGENTIC_MCP_BASE_URL:-http://backend:8080}
      TESSARY_CLASSIFIER_TRIAGE_MCP_BASE_URL: ${TESSARY_RCA_AGENTIC_MCP_BASE_URL:-http://backend:8080}
      # The groundedness model server (classifiers/groundedness/serve.py) on a GPU outside this stack. Blank
      # skips groundedness sweeps. The setup guides in classifiers/groundedness/setup/ write these to .env.
      TESSARY_OBSERVER_ENCODER_URL: ${TESSARY_OBSERVER_ENCODER_URL:-}
      TESSARY_OBSERVER_ENCODER_API_KEY: ${TESSARY_OBSERVER_ENCODER_API_KEY:-}
      TESSARY_GROUNDEDNESS_CLASSIFIER_MODE: ${TESSARY_GROUNDEDNESS_CLASSIFIER_MODE:-}
      # `memory` (default) or `kafka` (the `redpanda` service). No depends_on: with kafka set, a broker still
      # starting sheds with 503 + Retry-After.
      TESSARY_INGEST_SPOOL_MODE: ${TESSARY_INGEST_SPOOL_MODE:-memory}
      TESSARY_INGEST_SPOOL_KAFKA_BOOTSTRAP_SERVERS: ${TESSARY_INGEST_SPOOL_KAFKA_BOOTSTRAP_SERVERS:-redpanda:9092}
      # Interpolated rather than left to env_file so the registry install still reads .env (rule 3). 0 keeps forever.
      TESSARY_RETENTION_ENABLED: ${TESSARY_RETENTION_ENABLED:-true}
      TESSARY_RETENTION_TRACE_TTL_DAYS: ${TESSARY_RETENTION_TRACE_TTL_DAYS:-90}
      TESSARY_RETENTION_DETECTION_TTL_DAYS: ${TESSARY_RETENTION_DETECTION_TTL_DAYS:-90}
      # Interpolated for the same reason: via env_file alone, a registry install setting false keeps sending.
      TESSARY_TELEMETRY_ENABLED: ${TESSARY_TELEMETRY_ENABLED:-true}
    depends_on:
      postgres:
        condition: service_healthy
    # The runtime image has no curl or wget, so bash's /dev/tcp makes the request. /actuator/health is the
    # path AuthFilter admits without a session, and it is 200 only once the database is UP.
    healthcheck:
      test: ["CMD", "bash", "-c", "exec 3<>/dev/tcp/127.0.0.1/8080 && printf 'GET /actuator/health HTTP/1.0\\r\\nHost: localhost\\r\\n\\r\\n' >&3 && grep -q '\"status\":\"UP\"' <&3"]
      interval: 5s
      timeout: 3s
      retries: 60
      start_period: 15s
    restart: unless-stopped
    networks:
      - tessary
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
        tag: "tessary/backend"

  frontend:
    image: ${FRONTEND_IMAGE:-tessaryai/tessary:frontend-${TESSARY_VERSION:-1.11.1}}
    # Long syntax (rule 2). `published` must stay a quoted string to carry the ${VAR}.
    ports:
      - mode: ingress
        target: 8000
        published: "${HTTP_PORT:-80}"
        protocol: tcp
      - mode: ingress
        target: 443
        published: "${HTTPS_PORT:-443}"
        protocol: tcp
    environment:
      BACKEND_URL: backend:8080
      # frontend/caddy/render.sh builds the Caddyfile from these; blank SITE_DOMAIN means no TLS site.
      # TLS_MODE: acme (ACME_EMAIL required), owncert (certs at /certs) or upstream (plain HTTP behind your own
      # terminator, listed in TRUSTED_PROXIES).
      SITE_DOMAIN: ${SITE_DOMAIN:-}
      TLS_MODE: ${TLS_MODE:-acme}
      ACME_EMAIL: ${ACME_EMAIL:-}
      TRUSTED_PROXIES: ${TRUSTED_PROXIES:-}
      TLS_CERT_FILE: ${TLS_CERT_FILE:-/certs/tls.crt}
      TLS_KEY_FILE: ${TLS_KEY_FILE:-/certs/tls.key}
    volumes:
      # Persist ACME state so redeploys do not hit Let's Encrypt rate limits.
      - caddy-data:/data
      - caddy-config:/config
    # Waits on the backend serving, so a healthy frontend means the whole browser path is open.
    depends_on:
      backend:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "wget", "-q", "-O", "/dev/null", "-T", "3", "http://127.0.0.1:8000/"]
      interval: 5s
      timeout: 3s
      retries: 12
      start_period: 5s
    restart: unless-stopped
    networks:
      - tessary
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
        tag: "tessary/caddy"

  # Opt-in single-node Redpanda: `--profile kafka` plus TESSARY_INGEST_SPOOL_MODE=kafka. Internal only.
  # The image needs `developer_mode` to start under 1 GiB, which makes rpk bypass fsync, so
  # `--unsafe-bypass-fsync=false` is passed explicitly (rpk's warning is about the bypass it would add).
  # Write caching and the metrics reporter are off. `--set` must be two tokens.
  redpanda:
    image: docker.redpanda.com/redpandadata/redpanda:v25.2.1
    profiles: ["kafka"]
    command:
      - redpanda
      - start
      - --smp=1
      - --memory=512M
      - --reserve-memory=0M
      - --overprovisioned
      - --check=false
      - --unsafe-bypass-fsync=false
      - --set
      - redpanda.write_caching_default=false
      - --set
      - redpanda.enable_metrics_reporter=false
      - --set
      - redpanda.enable_usage=false
      - --kafka-addr=internal://0.0.0.0:9092
      - --advertise-kafka-addr=internal://redpanda:9092
    volumes:
      - redpanda-data:/var/lib/redpanda/data
    healthcheck:
      test: ["CMD-SHELL", "rpk cluster health | grep -E 'Healthy:.+true' || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 12
      start_period: 10s
    restart: unless-stopped
    networks:
      - tessary
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  # One-shot: a fresh named volume is root-owned, so this chowns it to the launcher's uid 1000.
  sandbox-runner-work-init:
    image: busybox:1.36
    command: ["sh", "-c", "mkdir -p /launcher-work && chown 1000:1000 /launcher-work"]
    volumes:
      - sandbox-work:/launcher-work
    restart: "no"

  sandbox-runner:
    depends_on:
      sandbox-runner-work-init:
        condition: service_completed_successfully
    # Internal only, at http://sandbox-runner:8080. Deliberately no `env_file: .env`, so it receives only
    # its own minimal secrets. Not profile-gated: RCA reaches it on every run; mem_limit is the guard.
    image: ${SANDBOX_RUNNER_IMAGE:-tessaryai/tessary:sandbox-runner-${TESSARY_VERSION:-1.11.1}}
    expose:
      - "8080"
    # The launcher's unauthenticated GET /healthz; a launcher that rejects its config shows as unhealthy.
    healthcheck:
      test: ["CMD", "wget", "-q", "-O", "/dev/null", "-T", "3", "http://127.0.0.1:8080/healthz"]
      interval: 5s
      timeout: 3s
      retries: 12
      start_period: 5s
    # Grants the docker.sock group rather than running as root. 0 fits Docker Desktop (root:root 0660). On
    # Linux set DOCKER_SOCK_GID to the group as a container sees it: `docker run --rm -v
    # /var/run/docker.sock:/var/run/docker.sock busybox:1.36 stat -c %g /var/run/docker.sock`.
    group_add:
      - "${DOCKER_SOCK_GID:-0}"
    volumes:
      # SECURITY: read-write docker.sock is host-root-adjacent for the launcher process. Inherent to spawning
      # sibling agent containers; mitigated by running non-root with no other host mount. Unconditional so
      # switching SANDBOX_BACKEND is one env var.
      - /var/run/docker.sock:/var/run/docker.sock
      # A named volume (rule 1): the daemon resolves a volume name the same from a container and the host.
      # Each sibling mounts its per-run subdirectory via VolumeOptions.Subpath.
      - sandbox-work:/launcher-work
    environment:
      PORT: "8080"
      # Same placeholder as the backend's: an empty key makes the launcher reject every request.
      SANDBOX_API_KEY: ${TESSARY_OBSERVER_AGENTIC_LAUNCHER_API_KEY:-Q0hBTkdFLU1FLWluc2VjdXJlLWRlZmF1bHQtbGF1bmNo}
      # Blank falls through to the code default, `docker`.
      SANDBOX_BACKEND: ${SANDBOX_BACKEND:-}
      # Cuts agent containers off this network. Turning it on requires SITE_DOMAIN and pointing
      # TESSARY_RCA_AGENTIC_MCP_BASE_URL at the public origin.
      SANDBOX_NETWORK_ISOLATION: ${SANDBOX_NETWORK_ISOLATION:-}
      # Pulled lazily by the launcher, not by `docker compose pull` or `--build`. Before the first release no
      # tag exists; set AGENT_IMAGE to a local image.
      AGENT_IMAGE: ${AGENT_IMAGE:-tessaryai/tessary:agent-sandbox-${TESSARY_VERSION:-1.11.1}}
      SANDBOX_DOCKER_CONCURRENCY: "1"
      # Must equal the `sandbox-work` volume's `name:`; the docker backend fails at boot if unset.
      SANDBOX_WORK_VOLUME: ${SANDBOX_WORK_VOLUME:-tessary-sandbox-work}
      E2B_API_KEY: ${E2B_API_KEY:-}
      # Namespaced so any E2B key can reach the public template; unset floats to `:latest`.
      E2B_ANALYZER_TEMPLATE: ${E2B_ANALYZER_TEMPLATE:-tessary/tessary-agent-sandbox:${TESSARY_VERSION:-1.11.1}}
      # The launcher reads no provider secret from its env; each run carries the org's own credential.
      # MANTLE_PROJECT_ID is an attribution scope, not a secret.
      MANTLE_PROJECT_ID: ${TESSARY_MANTLE_PROJECT_ID:-}
    # Below host RAM so an OOM kills this container alone. Separate from SANDBOX_DOCKER_MEMORY_MB, which caps
    # each agent container.
    mem_limit: 1536m
    restart: unless-stopped
    networks:
      - tessary
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
        tag: "tessary/sandbox-runner"

networks:
  tessary:
    driver: bridge

volumes:
  redpanda-data:
  caddy-data:
  caddy-config:
  # No explicit `name:`, so it is project-prefixed and a clean-room boot cannot touch a developer's database.
  tessary-postgres-data:
  # Explicit `name:` is load-bearing: the launcher hands this exact string to the Engine API. It is not
  # isolated by COMPOSE_PROJECT_NAME, so a clean-room boot must set a run-scoped SANDBOX_WORK_VOLUME.
  sandbox-work:
    name: ${SANDBOX_WORK_VOLUME:-tessary-sandbox-work}

Docker commands

docker compose -f oci://tessaryai/tessary:compose up

Use the above command to pull and run the Compose file. Learn more⁠.

Images used

Image + 1 more

Busybox base image.


Pulls

1B+

Stars

3522

Last Updated

24 days

Image

Open-source vector similarity search for Postgres


Pulls

100M+

Stars

112

Last Updated

16 days

Image

Redpanda is the real-time engine for modern apps.


Pulls

10M+

Stars

32

Last Updated

about 23 hours