Skip to content
TestLookupTestLookup
Search Ctrl K

Search TestLookup

Enter a term to search the local static index.

    Application deployment guide

    To install TestLookup:

    1. Pick an install mode.
    2. Prepare its secrets and storage.
    3. Run the database updates (migrations).
    4. Start the application.
    5. Check it with the health checks.

    If you have not chosen a mode yet, start with Pick a deployment mode further down this page. Each mode has its own guide with the step-by-step commands.

    You can install TestLookup in several ways:

    • with Docker Compose on one machine;
    • on Kubernetes;
    • on OpenShift, Red Hat’s version of Kubernetes;
    • on a single cloud VM;
    • on K3s (see the homelab guide);
    • as an offline install, with no internet connection.

    Compose needs Docker Engine or Docker Desktop with Compose v2, and installing from a clone also needs Git, GNU Make and Bash. The system requirements page lists what you need in more detail.

    Whichever mode you pick:

    • Choose one exact application version, and use images whose tag never changes.
    • Provide PostgreSQL, MongoDB, a Redis-compatible server, S3-compatible file storage, enough capacity, a way in for web traffic (ingress) and a trusted network.
    • Keep the web app, API, MCP server and workers on the same version.

    The bundled Redis-compatible server is Valkey 8.1, and the bundled S3-compatible file store is versitygw.

    Each published image is tagged with the ID of the commit it was built from (:<commit-sha>), and :latest moves with every change on main. A :vX.Y.Z tag exists only after a tagged release. Use a commit ID or a release tag instead of latest.

    Each install mode has its own guide on GitHub with the commands:

    • Compose: the demo and development stack, the smaller dev-lite stack, release images without a clone, the setup for sites with no internet connection, and the monitoring add-on.
    • Kubernetes: the base and the general-purpose overlays.
    • K3s: one node or more. The homelab guide on this site walks through it.
    • OpenShift: the standard setup, and a setup with no internet connection that pulls everything from one Artifactory server.
    • Single VM and Cloud Run.
    • Managed clouds: EKS with Terraform, GKE and AKS.

    The sizing guide on GitHub gives three starting profiles. A test result here means one result for one test.

    ProfileTest results a dayWhere it runsCPUMemoryDisk for one year
    SmallUp to 50,000One Compose host4 vCPU8 GB100 GB SSD
    MediumUp to 500,000One large host or a small Kubernetes cluster8 vCPU16 GB500 GB SSD
    Large1 million or moreKubernetes16 or more vCPU (estimate)32 GB or more (estimate)1 TB or more of NVMe (estimate)

    These figures exclude a local AI model. The local model add-on adds about 4 vCPU, 8 GB of memory and 10 GB of disk for the default qwen2.5:7b-instruct. make pull-llm also downloads qwen2.5:14b-instruct-q5_K_M and the embedding model, which need more disk.

    The profiles are starting points from the shipped configuration, not results of load tests, and the large profile and the growth rates are estimates. After a week of real use, measure your own volume. make preflight prints the current volume sizes.

    An air-gapped install runs on a network with no internet connection at all. The air-gapped install guide on GitHub explains the steps:

    1. On a machine with internet access, build an offline bundle with make offline-bundle.
    2. Copy the bundle to the install site and check it.
    3. Import it with scripts/release/import-bundle.sh. The script refuses a bundle whose checksum does not match, loads the images and renames them under one registry.
    4. Deploy on Compose, Kubernetes or OpenShift.

    The bundle holds the application, PostgreSQL, Valkey, versitygw and busybox images with their licence texts. It also holds software bills of materials (SBOMs) when the build machine has syft installed. MongoDB is not in the bundle, and the Ollama and ChromaDB images are added only with --with-llm.

    Supply the production secrets through your install mode’s own secret store: the application, sign-in token (JWT), webhook, database, file-storage, integration and AI provider secrets. Required secrets by mode below lists them.

    Before you open TestLookup to other users, set up TLS, trusted proxies, public addresses, certificate checks and key rotation. If your company has its own certificate authority, set HTTP_CA_BUNDLE instead of turning certificate checks off. A secret setting can also point to an entry in Vault or OpenBao when the installation is set up for it.

    Before you send traffic to a new version:

    1. Back up PostgreSQL, MongoDB and file storage.
    2. Run the database update job (migration) with the matching backend image.
    3. Check that the database reached the expected migration version.

    In this version, the latest migration is 0255. A ready API does not show that the migrations finished.

    Upgrade one release line at a time, to the latest patch of the target line. Read the upgrade notes for every version you pass through. On Compose, from a clone:

    1. Run make backup.
    2. Run make preflight TAG=vX.Y.Z. It checks the image tags, the pending migrations and the free disk space.
    3. Run make upgrade TAG=vX.Y.Z. It pulls or rebuilds the images and runs alembic upgrade head before it replaces the application. It then restarts the services in order and runs a quick smoke check. It does not roll back on its own, so the backup from step 1 is your way back.

    Release-image Compose sets TESTLOOKUP_VERSION, and its db-migrate service runs the migrations first. Kubernetes runs the migrations before the rollout, with scripts/run-k8s-migrations.sh or make k8s-migrate. On K3s, run this from the repository root (or make k8s-deploy-homelab-update):

    Terminal window
    homelabsetup/deploy-homelab.sh --skip-registry --skip-models

    Finish by checking that /health/ready returns ok and that /health/version reports the new commit. On Compose the backend answers on port 8000:

    Terminal window
    curl http://localhost:8000/health/ready

    Start a worker for every required queue and exactly one scheduler (beat). Check that the agent_children queue and every configured ingestion shard queue have a worker reading them. Count the API and worker processes in the PostgreSQL connection budget.

    TestLookup enforces that budget. The shipped configuration assumes:

    • PostgreSQL allows 400 connections (max_connections), with 50 kept in reserve for maintenance work.
    • Each API process uses 2 pooled connections plus 1 extra when busy.
    • Each worker process uses 1 pooled connection plus 1 extra.

    With APP_ENV=production, the API refuses to start when the database offers fewer connections than PG_FLEET_MAX_CONNECTIONS (400 by default). Compose also runs a db-budget-check service before the application starts. Before you add copies of a service or raise how many jobs each worker runs, recalculate the budget with the connection-budget guide on GitHub.

    A local AI model’s needs for memory, graphics memory (VRAM), disk, context length and parallel requests depend on the models you choose. The upgrade notes put the default qwen2.5:7b-instruct at about 5 GB of disk and 6 to 8 GB of memory while it runs. Model files (weights) and embedding files are kept outside Git.

    An offline installation needs its images, software packages and model files copied in and checked before you cut it off from the network. The offline bundle never includes model files. Build an offline model pack instead:

    1. On a machine with internet access, pull the exact LLM_MODEL and EMBEDDING_MODEL versions.
    2. Archive the Ollama model folder and write a SHA-256 checksum file.
    3. After you copy the pack across, check it with sha256sum -c.
    4. Restore it into the Compose volume, or into the K3s or OpenShift volume claim.

    Check these health addresses:

    • /health/live: the API process is running.
    • /health/ready: the services it needs are ready.
    • /health/version: the version that is running.
    • /health/details: the state of each service. It can return HTTP 200 while one of them is only partly working, so read the response.

    Then check that workers are taking jobs from the queues, that the database is at the expected migration, and that metrics are being collected. Finally, send one real report all the way through to a finished report, under your own review and integration settings.

    Before an upgrade, keep the IDs of the previous images and a matching backup of PostgreSQL, MongoDB and file storage, plus the keys and a recovery plan. Valkey (or Redis) and model files are not in the backup. You rebuild or reload them instead; see Backups below. Going back to an older database version, or restoring a backup, can destroy data, and it is not safe after a migration that changed data.

    Moving an installation from before 0.1.0 also needs the upgrade notes:

    • Valkey cannot read the old Redis data.
    • versitygw starts empty until you copy the stored files into it.
    • ChromaDB collections must be rebuilt.

    The same notes cover these changes:

    • MongoDB is no longer in the offline bundle.
    • There is a new default model.
    • The manual_upload flag.
    • Administrator accounts: there are no default passwords, and an existing administrator is never changed.
    • A release install from before 0.1.0 that kept a development .env with passwordless sign-in.
    • The homelab site configuration moved to TESTLOOKUP_HOMELAB_ENV.
    • The new image locations.

    The rest of this page is TestLookup’s own deployment guide. It has a table to help you pick a mode, the services every installation runs, the secrets each mode needs, TLS, start-up order, backups and health checks.

    TestLookup ships one set of images (backend, frontend and MCP server) and several ways to run them. This guide helps you pick a mode and lists what each mode needs. It also covers the tasks that every mode shares: secrets, TLS, backups, start-up order, upgrades and health checks. Each mode has its own guide with the commands.

    Whichever mode you choose, check your own installation with the health checks at the end of this guide.

    ModeWhat you runDatastoresTypical useGuide
    Compose: demo and developmentdocker-compose.yml via make quickstart, make dev, make dev-llmIn ComposeEvaluation, local developmentCompose
    Compose: dev-litedocker-compose.dev-lite.yml via make dev-liteIn ComposeLow-resource laptopsCompose
    Compose: release images, no cloneinstall.sh downloads docker-compose.release.yml and pulls prebuilt imagesIn ComposeA single host running published imagesCompose
    Compose: air-gappeddocker-compose.release.yml + docker-compose.airgap.ymlIn ComposeHosts with no internet accessCompose, air-gapped install
    Compose: monitoring add-ondocker-compose.monitoring.yml layered on the stackn/aPrometheus, Grafana and Jaeger next to ComposeCompose
    Single VM (GCP)docker-compose.yml + docker-compose.gcp-vm.ymlIn Compose on the VMA small team on one cloud VMSingle VM
    Cloud Run templatesinfra/cloudrun/*Managed services you provideServerless API, web app and MCP server, with managed data servicesSingle VM and Cloud Run
    Kubernetes: generic overlaysk8s/base + k8s/overlays/{dev,staging,prod,self-hosted}Provided by you (managed or in-cluster)Any conformant clusterKubernetes
    Kubernetes: K3s (one node or more)k8s/overlays/homelab + homelabsetup/deploy-homelab.shIn-clusterOne machine, plain HTTP on a private networkHomelab guide
    OpenShiftk8s/overlays/openshiftProvided by you (Valkey or Redis must save its data to disk)OpenShift with RoutesOpenShift
    OpenShift + Artifactoryk8s/overlays/openshift-artifactory + openshiftsetup/In-clusterAir-gapped OpenShift that pulls from one registryOpenShift
    Offline bundlemake offline-bundle, scripts/release/import-bundle.shCompose, Kubernetes or OpenShiftSites with no internet connection at allAir-gapped install
    AWS EKSinfra/aws/ (Terraform) + k8s/overlays/aws-eks + scripts/deploy-k8s.shRDS, DocumentDB, ElastiCache, S3AWS with managed data servicesManaged clouds
    Google GKEk8s/overlays/gcp-gke + scripts/deploy-k8s.shProvided by youGKE with Google-managed certificatesManaged clouds
    Azure AKSk8s/overlays/azure-aks + scripts/deploy-k8s.shProvided by youAKS with NGINX ingressManaged clouds

    Rules of thumb:

    • Trying TestLookup: run make quickstart from a clone, or use the one-line install.sh command shown in the quickstart if you do not want a clone.
    • One team, one host: release images on Compose, with backups (make backup) on a schedule.
    • Several teams, or you need several copies of each service and upgrades without downtime: Kubernetes with managed PostgreSQL, MongoDB, Valkey or Redis, and S3-compatible storage.
    • No internet at the install site: the offline bundle. Models are prepared separately with the offline model pack.

    The sizing guide gives the hardware for each profile (small, medium and large) and for the local AI model add-on.

    ComponentImage or serviceNotes
    Frontendtestlookup/frontendnginx serving the React app; proxies /api/, /ws/ and /webhooks/ to the backend
    Backend APItestlookup/backendFastAPI on port 8000; Swagger at /api-docs
    Workers and schedulertestlookup/backend running CeleryQueues critical, ingestion (plus shard queues), ai_analysis, default, and the isolated agent_children queue; one beat scheduler
    MCP servertestlookup/mcpSSE on port 8002 (bearer-authenticated) or stdio
    PostgreSQL 16postgres:16-alpineThe main database, which holds the official records
    MongoDB 7mongo:7Evidence, summaries and decision documents. MongoDB is licensed SSPL-1.0 and is not included in the offline bundle; provide it yourself there
    Valkey 8valkey/valkey:8.1-alpineJob queue and results for the background workers (Celery), live-event streams and caches. The settings keep their REDIS_* names. Any Redis-compatible server that saves its data as described below works
    S3-compatible object storeghcr.io/versity/versitygw (Apache-2.0)Uploads, reports, exports, knowledge documents. Settings keep the MINIO_* names and point at any S3-compatible endpoint
    Ollama (optional)ollama/ollamaLocal models. Default chat model qwen2.5:7b-instruct, embeddings nomic-embed-text:v1.5
    ChromaDB (optional)chromadb/chromaStores the number summaries (vectors) used for search by meaning and knowledge retrieval

    deploy/images.manifest.txt is the one list of every image and tag. make images-check fails when Compose, Kubernetes or the mirror scripts no longer match it.

    The backend refuses to start with APP_ENV=production or staging when APP_SECRET_KEY, JWT_SECRET_KEY or WEBHOOK_SECRET is still a placeholder, when the password inside MONGO_URI is a placeholder, or when DEV_AUTO_LOGIN_ENABLED is true (config checks). Generate each secret with openssl rand -hex 32.

    ModeWhere secrets liveKeys you must set
    Compose (all variants).env next to the compose file. make quickstart generates it with scripts/gen-dev-env.sh (development mode); install.sh uses scripts/gen-dev-env.sh --release (production mode)POSTGRES_PASSWORD, MONGO_PASSWORD, MINIO_ACCESS_KEY, MINIO_SECRET_KEY (Compose refuses to start without them), FLOWER_PASSWORD (source-built stack), APP_SECRET_KEY, JWT_SECRET_KEY, WEBHOOK_SECRET; REDIS_PASSWORD turns on broker authentication; INITIAL_ADMIN_PASSWORD for the first admin of a release install; GF_SECURITY_ADMIN_PASSWORD for the monitoring add-on
    Single VM (GCP).env copied from .env.gcp-vm.exampleThe Compose keys above plus GCP_PROJECT_ID and IMAGE_TAG (required by docker-compose.gcp-vm.yml)
    Kubernetes, all overlaysSecret testlookup-secrets in the target namespaceAPP_SECRET_KEY, JWT_SECRET_KEY, DATABASE_URL, POSTGRES_PASSWORD, MINIO_ACCESS_KEY, MINIO_SECRET_KEY, MONGO_URI, WEBHOOK_SECRET; optional provider and integration tokens. See Kubernetes secrets
    OpenShift + ArtifactoryCreated by deploy-openshift-artifactory.sh on first installGenerated; you supply only the Artifactory credentials in artifactory.env
    EKS via TerraformAWS Secrets Manager (written by Terraform)Synced into testlookup-secrets before deploy

    Never commit .env, openshiftsetup/artifactory.env or a filled-in Secret manifest. Losing APP_SECRET_KEY makes saved encrypted credentials (integration tokens) unreadable even after a database restore, so back it up with your secrets store.

    A production-mode installation has no passwordless login. The script scripts/create_admin.py creates the first ADMIN user on a database that has none, and it never changes an existing admin. If you supply no password, it generates one and prints it once. The new admin must change that password at the first sign-in.

    • Release install (install.sh): the installer prints the admin login once; the password is stored in .env as INITIAL_ADMIN_PASSWORD, and the db-migrate service creates the user admin with it on first start. See release images.
    • Source-built Compose: make create-admin runs the script in the backend container. The local demo stack (make quickstart, make dev) runs in development mode and uses passwordless Quick Login instead.
    • Kubernetes and OpenShift: scripts/deploy-k8s.sh and openshiftsetup/deploy-openshift-artifactory.sh create the admin at first install and print the generated password once; make create-admin-k8s runs the script by hand.
    ModeHow traffic reaches TestLookupTLS
    ComposeFrontend on host port 3000 (source build) or 3000 mapped to nginx port 80 (release images); backend on 8000; MCP server on 8002, reachable only from the same machineNone built in. Put a reverse proxy (nginx, Caddy or Traefik) in front and let it handle HTTPS
    Single VMFrontend nginx on port 80Add a reverse proxy or load balancer with a certificate before exposing it publicly
    Kubernetes prod, self-hosted and azure-aksNGINX Ingress testlookup-ingress (/api/, /api-docs, /ws/, /webhooks/, /health/, /metrics to the backend, / to the frontend) and testlookup-mcp-ingress on its own hostcert-manager annotation cert-manager.io/cluster-issuer: letsencrypt-prod, Secrets testlookup-tls-cert and testlookup-mcp-tls-cert
    GKEGCE IngressGoogle-managed certificate (managed-cert.yaml)
    EKSAWS Load Balancer Controller (ALB)ACM certificate ARN annotation
    OpenShiftRoutes that handle HTTPS at the router and redirect HTTP to HTTPSRouter default wildcard certificate, or your own PEM files
    K3s (homelab)Traefik on plain HTTPPrivate networks only; see the homelab guide

    Behind any proxy, set CORS_ORIGINS and PUBLIC_BASE_URL to the address users open. Set TRUSTED_PROXY_IPS to the proxy’s address range only, and never to *. The API uses it to find the real client address for rate limits and the activity log. The overlays include commented-out defaults for each platform.

    1. Choose an image tag or image ID that never changes, and fix your configuration. Supply the production secrets, addresses, TLS and trusted-proxy settings. Keep the web app, API and MCP server on matching versions.
    2. Set up the services that keep data, and check they have enough capacity. The PostgreSQL connection budget must count the API and worker processes, the reserved connections and the connection the migration needs. See database connection budget.
    3. Run the database update job (migration) up to the target version, with the matching backend image. On Compose this is the db-migrate service, and on Kubernetes it is scripts/run-k8s-migrations.sh. A ready API does not show that the migration finished.
    4. Start the API, a worker for every required queue and exactly one scheduler (beat). Check that the agent_children queue and every configured ingestion shard queue have a worker reading them.
    5. Check the ingress paths, /health/version, readiness and metrics. Then send one report all the way to a finished report, and check that integrations behave as expected under your real settings.

    On Compose, run make backup, then make preflight TAG=..., then make upgrade TAG=.... Together they follow this order. Read the ops scripts first. upgrade.sh pulls the new images (release stack) or rebuilds them (source stack), and runs alembic upgrade head before it replaces the application. It then restarts the services in order and runs a quick smoke check. It does not roll back on its own. The backup you took before the upgrade is the way back. After a migration that changed data, going back one database version is not a safe way to undo a release. Keep your backups and the IDs of the previous images.

    Version-specific steps, such as moving an existing install from Redis to Valkey, are in the upgrade notes (UPGRADING.md). To change LIVE_INGEST_SHARD_COUNT, change the services that send work and the workers at the same time, following the ingestion shard count change runbook.

    ModeToolingWhat it covers
    Composemake backup (QUIESCE=1 stops the app first, BACKUP_DIR sets the destination) and make restore FILE=... [FORCE=1] [CONFIRM=yes]PostgreSQL dump, MongoDB archive, object-store data and a manifest with the Alembic head, in one .tar.gz
    KubernetesThe backup component: a daily CronJob to a PVC plus an on-demand restore JobSame three stores; enabled in the K3s and OpenShift + Artifactory overlays, opt-in elsewhere
    Managed servicesYour provider’s snapshots and point-in-time recoveryBack up PostgreSQL, MongoDB and the bucket together

    Valkey (or Redis), ChromaDB and Ollama are left out of the archive on purpose. They can be rebuilt, and replaying an out-of-date job queue after a restore is dangerous. Back up PostgreSQL, MongoDB and file storage together so that the links between them stay correct. Copy the archives to another machine, and test a restore. For step-by-step backup and restore, see storage and backups and the administration guide.

    Valkey (or Redis) is more than a cache. It is the job queue for the background workers (the Celery broker), and it holds live-event streams and temporary status, so a Valkey failure affects all of them. The Compose and in-cluster setups save its data to an append-only file every second (appendfsync everysec). They use the volatile-lru policy, which removes only keys that have an expiry time (TTL) when memory runs low. Even so, some data can still be lost. If you run your own Valkey or Redis, it must save its data to disk and must never remove keys that have no expiry time. The OpenShift page lists all the requirements. Give it enough memory for your event and queue volume, and watch the levels at which TestLookup starts refusing new work. Separate child workers and the database connection budget limit how much work runs at once. If you add copies of a service without recalculating database connections, TestLookup can refuse to start or use up all the database’s connections.

    How much memory, graphics memory (VRAM) and disk a local model needs depends on the model, how much it is compressed (quantization), the context length and how many requests run at once. Measure it on the machine you will use. Model files are kept outside Git, in a Compose volume or the deployment’s storage. make dev-llm downloads the models, so it needs internet access. A site with no internet connection loads them from an offline model pack. Offline mode (AI_OFFLINE_MODE=true, the default) blocks hosted AI providers and other outgoing AI calls, but still allows local services such as Ollama. It does not replace cutting the server off from the network.

    On K3s, local-path volumes keep data on one machine’s disk. Extra copies of the application do not protect you if that disk fails, so keep backups on another machine.

    SignalWhat it tells you
    /health/liveThe API process can answer. Do not restart other services based on this alone
    /health/readyPostgreSQL, MongoDB and Valkey (or Redis) are ready. A 503 takes the pod out of traffic
    /health/versionThe version, commit, build and environment
    /health/detailsA short report on each service it depends on. It can return HTTP 200 while some of them are only partly working
    /metricsCounters and gauges for HTTP requests and TestLookup’s own work, when METRICS_ENABLED is on. Queue lengths are refreshed each time the metrics are read
    Worker and beat logs, FlowerWhich jobs were sent, picked up and retried. On Compose, Flower listens only on the local machine
    Project activity and decision trailWhat users did, and the reasons and sources behind each agent’s work
    Upload, outbox, dead-letter queue and pipeline recordsWhich background step failed, beyond what HTTP health shows

    Prometheus alert rules live in infra/monitoring/prometheus-rules/; Compose loads them through the monitoring add-on and Kubernetes through k8s/monitoring. Set alerts for pipelines that stop moving, and for leases or retries that run out. Also alert on jobs piling up in the outbox or the dead-letter queue (a holding area for messages that kept failing), uneven queues, database connections and the job queue’s memory. Installing an OpenTelemetry library does not on its own trace the Celery tasks. See the monitoring guide and the troubleshooting page.

    Next, read day-two operations for regular checks and recovery.