Local development setup
Last Updated: 2026-09-28 Status: Alpha This is the end-to-end walkthrough for getting Poindexter running on your own machine. If you only want the shortest path to a first post, follow the Quickstart — the same sequence thequickstart-e2e CI job runs on a clean runner (which has no GPU, so it
stands one tiny model in for the three large ones and zeroes the quality
bar) and requires to end with a post in the approval queue.
This document covers the longer form: what the setup wizard does
under the hood, how to verify each layer, and how to troubleshoot
when something doesn’t come up.
Minimum hardware
RAM: 32 GB recommended. The default stack (
docker-compose.consumer.yml)
idles at roughly 4–6 GB; the operator stack (docker-compose.local.yml,
adding Langfuse, ClickHouse, Loki, Tempo and more) uses 8–12 GB.
Disk: about 50 GB free — 31 GB for the default models and ~13 GB for the
stack’s images (measured on a clean runner); more for larger writer models
and generated media.
1. Prerequisites
Windows note. Run all commands from Git Bash or WSL. Native
cmd.exe and PowerShell do not work with the start scripts.
Docker Desktop must be configured to use WSL2 backend.
Linux note. Stock Docker Engine on Linux does not automatically
resolve host.docker.internal — this is a Docker Desktop feature.
The compose files add extra_hosts: ["host.docker.internal:host-gateway"]
to every service that calls a host-side endpoint (Ollama, image-gen, voice
bridge). host-gateway resolves to the Docker bridge gateway (Docker
Engine 20.10+), so a container’s call arrives on the bridge interface,
not on loopback. A default Ollama install listens on 127.0.0.1 only
and refuses it: every model call fails. Make Ollama listen on every
interface once (firewall port 11434 if the machine is on a network you
don’t trust):
2. Clone and setup
--auto does the following:
- Generates secrets —
local_postgres_password,grafana_password,poindexter_secret_key(the key every encryptedapp_settingsrow uses), the Postiz pair, and the operator-stack extras — pluscompose_project_name, so every later launch lands in the same compose project. Re-running with--forcekeeps every value the file already holds: Postgres keeps the password its volume was initialised with, and encrypted settings need the key they were written with. - Starts the stack’s own Postgres — the
postgres-localservice of the compose filescripts/start-stack.shlaunches (docker-compose.consumer.ymlon a public clone), in that compose project. The host reaches it on port 5433 (POSTGRES_HOST_PORToverrides it, and the override is written tobootstrap.tomlso it sticks); every container reaches it aspostgres-local:5432. One database for the CLI and the stack. - Tests the connection and runs migrations, then seeds the
app_settingsdefaults. Safe to re-run — migrations are idempotent. - Writes
~/.poindexter/bootstrap.tomlwith the database URL + generated secrets. This is the only config file on disk. - Provisions the initial CLI OAuth client — registers a row in
oauth_clients, encrypts the credentials intoapp_settings.cli_oauth_client_id/cli_oauth_client_secret, and prints the plaintext secret once for capture. (Worker auth uses OAuth 2.1 only as of Glad-Labs/poindexter#249.)
.env file is created. All secrets live in bootstrap.toml
(safe permissions, never committed to git).
Older releases’--autostarted a separatepoindexter-postgres-autocontainer on port 5434. The stack never used it — every container connects topostgres-local— so tasks queued from the CLI were never seen. If you have one,setup --autopoints it out; remove it withdocker rm -f poindexter-postgres-autoonce you have copied out anything you need.
3. Pull AI models
quickstart-e2e job: gemma3:27b (the
writer and every role seeded with it), phi4:14b (the critic that hard-gates
each draft, and the Ragas/DeepEval judges), llama3:latest (podcast and video
script drafting) and nomic-embed-text (embeddings). The pipeline does not pull
models on demand: a model Ollama lacks fails the call that names it. The roles
that make up this list live in poindexter/services/required_models.py; the
tags come from the seeded settings, and a unit test holds the README and the
Quickstart page to them. poindexter setup --auto prints the same command from
your database’s settings, so a role you re-pointed shows your model.
Pull these only when you switch the feature on: qwen3-vl:30b-a3b-instruct
(image QA and captions, ~20 GB — called only when a post has images),
qwen2.5:32b (fallback critic), granite4.2:3b (the brain’s alert triage) and
qwen2.5:7b (console chat and the voice agent). Every role is an app_settings
key (pipeline_writer_model, pipeline_critic_model, video_scene_model, …),
so a different writer is ollama pull <tag> plus
poindexter settings set pipeline_writer_model ollama/<tag>.
4. Bring up the stack
~/.poindexter/bootstrap.toml, exports the values as env
vars, and runs docker compose -f <file> up -d, where <file> is
docker-compose.consumer.yml on a public clone (or the operator’s
docker-compose.local.yml when the checkout has one). No .env file
needed; a bare docker compose up stops at the first ${VAR:?...}
sentinel because nothing else exports those values. The first run
builds the images, so allow several minutes.
The default stack starts these containers:
Opt-in profiles:
bash scripts/start-stack.sh --profile image-gen up -d
adds local image generation (GPU); --profile postiz adds the social hub.
Dispatch. Prefect is the only dispatcher. On its first start the
poindexter-prefect-worker container runs
deploy_content_flow.py --if-missing, which registers the
content-generation deployment (cron from
app_settings.prefect_content_flow_cron, default every two minutes)
unless it already exists. It never touches an existing deployment, so a
re-tuned cron or a paused deployment survives restarts; to roll out a
tuning change, run python -m scripts.deploy_content_flow from
src/cofounder_agent.
5. Verify
pending → in_progress → awaiting_approval.
Prefect picks it up within two minutes; the run itself takes minutes on a
GPU. Follow along via:
6. Frontend (optional for backend dev)
If you’re only iterating on the backend, skip the frontend — the worker’s API and the Grafana dashboards are all you need. If you do want the Next.js public site running locally:7. Run the tests
The unit suite runs lean — the cross-encoder reranker (Expected: the full unit suite passes (several thousand cases). Some tests that depend on thesentence-transformers
torch) is an opt-inrerankextra that CI deliberately skips, and the tests that touch itimportorskip. Addpoetry install --extras rerankonly if you want to exercise the reranker locally.
brain module or sentry-sdk are skipped when running
inside Docker (these pass on the host where all modules are available).
8. What to do when something breaks
See troubleshooting.md.Configuration
All runtime configuration lives in theapp_settings Postgres
table, not env vars. After setup, change settings with: