Skip to content

Docker Compose

Self-Contained Demo Layout

The demo uses four independent uv projects rather than a uv workspace:

Project Lockfile Deployment
demo/api demo/api/uv.lock ghcr.io/ilkersigirci/lgos-demo-api
demo/files_api demo/files_api/uv.lock ghcr.io/ilkersigirci/lgos-files-api
demo/ui/chainlit_ui demo/ui/chainlit_ui/uv.lock ghcr.io/ilkersigirci/lgos-chainlit
demo/ui/openwebui demo/ui/openwebui/uv.lock Host-run sync tool; unchanged pinned official Open WebUI image

Published project-owned images use only their project directories as build contexts. The Compose entrypoint is docker/compose/demo.yml; service definitions live in docker/apps/, while docker/compose/development.yml and docker/compose/otel.yml provide development and OpenTelemetry overlays. Shared runtime assets remain under demo/docker/. The development overlay additionally supplies the parent LGOS checkout as a named context for the API's editable install. Open WebUI runs its pinned official image unchanged. Its locked OpenAI v3 and HTTPX2 synchronization project runs on the host, while Compose mounts only the Function sources and small raw-upload policy into the upstream image. The two gateway fragments use pinned public images: upstream Bifrost and homeserver-litellm. There is no demo-wide pyproject.toml, uv workspace, shared Python environment, or shared lockfile. The API package includes the compact Markdown corpus used by lgos-rag.

Compose Modes

Commands use Bash and Just 1.58.0 or newer and run from the repository root. In a standalone copy of demo/, use just <recipe> in place of just demo/<recipe>.

Docker Compose 5.3.0 or newer

The demo is validated with Compose 5.3.0. Chainlit and the two API services use dedicated one-shot schema jobs and wait for successful completion. This avoids running the same persistence migrations concurrently in application workers and ensures the MCP views are created only after their source tables exist.

Prepare the demo environment:

cp demo/.env.example demo/.env

Set PUID and PGID in demo/.env to the numeric host identity that owns the bind directories. The checkout includes each empty service directory with a tracked .gitkeep; service-created contents remain ignored.

demo/.env.example owns the configurable demo defaults. Compose reads the corresponding values from demo/.env without supplying fallbacks. Keep an existing demo/.env in sync with new template settings without overwriting secrets.

Before using either OTEL mode, configure the OpenTelemetry settings.

demo/docker/compose/demo.yml contains no local builds:

just demo/compose

The command waits for the gateway and its dependencies, syncs LiteLLM when selected, starts both UIs without recreating the healthy backends, and syncs Open WebUI. Services remain running in the background. Compose owns dependency order and readiness; just only sequences the repeatable sync jobs.

Set DEMO_IMAGE_TAG in demo/.env to select one release tag for all project-owned demo images. To add the published OTEL overlay, use just demo/compose --otel.

Apply the explicit development model from the LGOS repository checkout. The API, Files API, and Chainlit services build locally from their Dockerfiles and lockfiles; Open WebUI remains the pinned upstream image. Only the API image installs the parent LGOS checkout as an editable package:

just demo/compose --dev

To add the OTEL overlay while building the current checkout, use just demo/compose --dev --otel.

The development overlay bind-mounts the Python application sources and the parent LGOS package read-only. Restart or recreate the affected service after source edits. Dependency metadata and lockfile changes require an image rebuild.

For immediate local feedback without containers, use uv's temporary editable overlay:

just demo/test --editable

This tests all four projects and does not rewrite api/pyproject.toml or api/uv.lock. Chainlit and Open WebUI remain standalone clients and exercise whichever API their OpenAI base URL targets.

Demo Services

One bundled gateway at a time

Bifrost and LiteLLM both publish host port 3000. Enable only the selected gateway's profile, and stop the running gateway before switching. Changing OPENAI_GATEWAY_TYPE does not stop the previous gateway container.

just demo/up lgos-postgres-mcp --wait

The API and Chainlit migration jobs initialize their persistence schemas. A following one-shot job creates or updates the dedicated lgos_mcp login and four curated views over live Chainlit users/conversations and pending LGOS interrupts. The role can read only those views. The pinned DBHub service then exposes six fixed reports only inside the Compose network; it publishes no host port. Starting either bundled gateway starts these dependencies automatically. Replace LGOS_MCP_DB_PASSWORD and LGOS_MCP_AUTH_TOKEN before startup. See PostgreSQL Through Native MCP for the complete client and security flow.

just demo/up lgos-demo-api-a
just demo/up lgos-demo-api-b

Run each attached service in a separate terminal. Compose starts the shared PostgreSQL dependency automatically. Before either graph API starts, lgos-demo-api-setup waits for PostgreSQL health and initializes the LangGraph checkpoint and store schemas once. Both APIs use service_completed_successfully as their readiness dependency.

  • lgos-a: http://localhost:3004/v1
  • lgos-b: http://localhost:3005/v1

For an independently deployed LiteLLM API, run the shared one-shot model-sync job after the deployment's health check.

just demo/up lgos-files-api

The independently packaged service connects directly to its configured S3-compatible store. It neither starts PostgreSQL nor imports LGOS.

  • central Files API: http://localhost:3006/v1
just demo/up lgos-bifrost

The UIs use native /openai/v1/responses, normal /v1 Files routing, the aggregate /mcp endpoint, and raw pass-through only for provider-specific catalog detail. The named PostgreSQL Virtual MCP remains available at http://localhost:3000/mcp/lgos-postgres. See Bifrost Gateway for endpoints, routing, and the shared SDK verification command.

Set these values in demo/.env to reuse an existing LiteLLM gateway:

OPENAI_GATEWAY_TYPE=litellm
COMPOSE_PROFILES=
OPENAI_GATEWAY_BASE_URL=https://litellm.example.com
DEMO_GATEWAY_HOST_URL=https://litellm.example.com
OPENAI_GATEWAY_API_KEY=TO_BE_FILLED
DEMO_CHAINLIT_ENABLE_OAUTH_TOKEN_FORWARDING=false

Use the gateway root without /v1. Both UIs share the gateway type, base URL, and OPENAI_GATEWAY_API_KEY, or Chainlit uses delegated OAuth when explicitly enabled. An external LiteLLM uses the same Responses, Files, and catalog routes as bundled LiteLLM.

Both UIs derive their MCP endpoint from the same gateway root and use the same credential as Responses and Files. To use mcp-postgres, also adapt the checked-in mcp_servers.lgos_postgres entry and LGOS_MCP_AUTH_TOKEN for the external gateway, just as the Files configuration is adapted below. The external gateway URL must be reachable from both UI containers. On another host, expose DBHub only on a private address reachable by that gateway.

just demo/compose (or the same command with --dev) starts the demo APIs, Files service, DBHub, and its PostgreSQL setup, syncs both catalogs to the external LiteLLM, then starts and syncs the UIs. An empty COMPOSE_PROFILES disables both bundled gateways; the template normally selects one through COMPOSE_PROFILES=${OPENAI_GATEWAY_TYPE}. The external gateway and its administrator credentials must be reachable by the model-sync job.

Enable LiteLLM's native database model storage. For Files, adapt the files_settings in docker/configs/litellm/config.yaml to the shared Files service. No LGOS catalog pass-through is required. Retain the bundled image's LITELLM_ENABLE_RESPONSES_STREAMING_FIX=true opt-in when using that image.

On the same Docker host, attach the existing LiteLLM service to the demo's network through its own Compose deployment, alongside its current networks:

services:
  litellm:
    networks:
      # Retain the service's existing networks here too.
      - lgos

networks:
  lgos:
    external: true
    name: lgos-network

Create the network by starting the demo backends first, for example with just demo/up lgos-demo-api-a, just demo/up lgos-demo-api-b, and just demo/up lgos-files-api in separate terminals, then just demo/up lgos-postgres-mcp --wait. The existing gateway can then resolve lgos-demo-api-a, lgos-demo-api-b, lgos-files-api, and lgos-postgres-mcp using the bundled sync, Files, and MCP configuration examples. For another host, replace those upstream URLs with addresses reachable from that gateway.

The external deployment continues to own its database, TLS, credentials, and Admin UI SSO. The selected credentials must allow the LGOS models, Files operations, the configured MCP tools, and native /model/info. Chainlit can enable delegated OAuth; it then ignores the shared static key and disables its native MCP connection. If the gateway already configures litellm_proxy Files, reconcile that provider with the demo's shared Files namespace. Then run just demo/sync-openwebui and the LiteLLM SDK checks against the external URL.

just demo/up lgos-litellm

LiteLLM is one of the two first-class UI entry points. After startup, sync both demo catalogs. The UIs use:

  • model metadata: http://localhost:3000/model/info
  • managed Files: http://localhost:3000/v1
  • managed routing: http://localhost:3000/v1
  • native MCP: http://localhost:3000/mcp/
  • LiteLLM Admin UI: http://localhost:3000/ui/

The bundled configuration uses API-key authentication. Chainlit can still use OAuth for browser login with that key. OAuth token forwarding additionally requires a gateway configured to validate delegated access tokens through its trusted SSO ingress; enabling forwarding in Chainlit alone does not configure LiteLLM. See Chainlit OAuth.

Chainlit and Open WebUI send Responses and Files to managed routing and read descriptions, capabilities, and settings from model_info.lgos. Each graph uses LiteLLM's model_name unchanged for inference. The proxy retains normal model routing while each API remains the source of its descriptions and LGOS capability metadata and the Files service remains the owner of file bytes. Neither UI connects to an upstream service directly. OPENAI_GATEWAY_API_KEY protects these routes in the default setup; replace its demo-only default in any shared deployment. For the local Admin UI, sign in as admin; unless UI_PASSWORD is set separately, the password is the value of OPENAI_GATEWAY_API_KEY from demo/.env.

The MCP configuration reaches DBHub with the separate internal LGOS_MCP_AUTH_TOKEN, allowlists the six fixed PostgreSQL reports, and permits authenticated gateway keys to use them. Native clients still opt in by connecting to /mcp/; the graph API receives only ordinary OpenAI function definitions and results.

The managed-routing surface uses concrete database-backed models and LiteLLM's native Responses endpoint. Select an API with a provider-qualified model, such as lgos-a/custom-input-output-context or lgos-b/custom-input-output-context. The shared demo PostgreSQL service keeps LiteLLM's Admin UI and gateway-management records in its own litellm schema; graph execution state remains owned by LGOS. LiteLLM's standard files_settings route uses provider=litellm_proxy to isolate upload, retrieval, content, and deletion from the graph deployments.

The LGOS-owned sync reads each API's model list and detail, then registers routing and model_info.lgos through LiteLLM's native management API. New deployments set model_info.supports_native_streaming: true. The default public homeserver-litellm image preserves native Responses streaming. Compose reads its tag and digest from DEMO_LITELLM_IMAGE in demo/.env and enables LITELLM_ENABLE_RESPONSES_STREAMING_FIX=true so it honors the deployment capability. Normal demo commands use this image without a local build or an image override.

To use another compatible image, set DEMO_LITELLM_IMAGE in demo/.env or supply it on the command line:

DEMO_LITELLM_IMAGE='registry.example.com/team/litellm:tag' \
  just demo/compose --dev --otel

Keep the override set for subsequent Compose commands. To restore the default, copy the image value from demo/.env.example. An alternative image must preserve native Responses streaming, authenticated /model/info with custom metadata, managed Files routing, and the Admin UI migration runtime.

Managed routing also passes the tested Files lifecycle, file-ID input, and function continuation, while its rewritten standard error metadata remains a strict expected failure. The bundled image records successful managed Responses requests in LiteLLM's spend logs, including streaming requests. Token and spend values reflect usage and pricing supplied for the selected graph model.

With the service healthy, run the focused OpenAI SDK check from the repository root. It tests managed routing, the catalog-to-inference flow, and native streaming fidelity against the direct LGOS test endpoints. LiteLLM exposes no demo pass-through routes:

just demo/test-litellm --editable

With the gateway and its backends running:

just demo/up lgos-chainlit

Chainlit: http://localhost:3002

This command starts Chainlit and PostgreSQL. Use just demo/compose to start the complete stack with the gateway selected by COMPOSE_PROFILES.

Configure its signing secret as described in the Chainlit client.

just demo/up lgos-openwebui --wait

Open WebUI: http://localhost:3003

Compose runs the pinned official Open WebUI image unchanged. Follow the Open WebUI setup to synchronize the bundled Functions and generate Workspace Models from LGOS metadata. The Compose service also mounts the temporary raw-upload policy described under Open WebUI file input.

PostgreSQL is published on localhost:3001. LangGraph persistence, Bifrost state, and Open WebUI state—including its native raw file copies—use host bind mounts under demo/docker/volumes/; the Compose model declares no named volumes. Every service runs as PUID:PGID with a read-only root filesystem, dropped capabilities, and explicit resource limits. Narrow tmpfs mounts hold required ephemeral writes. The one-shot API and Chainlit setup services initialize their respective persistence schemas. A following idempotent setup job owns the MCP reporting views, role, and grants before DBHub starts.

Chainlit stores thread and element metadata in PostgreSQL, while its native S3 client uploads generated file elements to the configured BUCKET_NAME. Resuming a thread obtains a fresh signed object URL from that client. The central Files API uses only its separate DEMO_API_FILES_BUCKET, DEMO_API_FILES_S3_ENDPOINT, and DEMO_API_FILES_AWS_* settings. The two S3 configurations are independent.

The API workers share PostgreSQL for thread-scoped application data, durable checkpoints, and fail-fast interrupt coordination. Session-level advisory locks prevent two workers from advancing the same interrupt run at once; a contended request fails instead of waiting. No Redis service is required. The lock is held only while an API request executes the graph, never while a human is deciding. A per-process capacity gate preserves a pool connection for persistence I/O.

Compose also forces LANGGRAPH_STRICT_MSGPACK=true for the APIs. Strict deserialization narrows which checkpoint object types LangGraph may reconstruct, following its security guidance. Protect the PostgreSQL credentials and storage as integrity-sensitive data as well.

PostgreSQL is sufficient state infrastructure, not a complete operations plan

The Compose database is a single demo container. A production deployment still owns tested backup and restore, monitoring, upgrades, and its chosen replication and failover guarantees. LangGraph's exit durability writes the resumable state when an invocation pauses or finishes; LGOS drains that invocation before exposing interrupt tool calls. Whether the resulting commit survives loss of the primary depends on the PostgreSQL replication policy.

Budget connections across every API replica. Each demo API process has a five-connection pool and permits at most four simultaneous interrupt leases, preserving one connection for checkpoint I/O. Psycopg recommends monitoring pool statistics and sizing from observed workload; see its pool guidance.

The coordinator uses session-level advisory locks and must retain one database session for the whole lease. If a proxy such as PgBouncer sits in front of PostgreSQL, use session pooling or a direct coordinator connection; PgBouncer documents session advisory locks as unsupported in transaction-pooling mode.

Demo images are examples

The published images run the demo applications and graphs. They are not generic LGOS server images and should not be used as the base contract for an application that owns different graphs or dependencies.

Applications outside demo/ own their container images and deployment model; LGOS does not prescribe either. For exact demo commands and environment variables, see Demo Settings and Commands.