AI Software Factory — Docker Dev Environment
Local development environment for the AI Software Factory monorepo. One
docker compose file gives you the full data layer (PostgreSQL, Redis, MinIO,
OpenSearch) plus, optionally, the application services (web, api, worker,
realtime).
The repo root has wrapper scripts:
pnpm docker:up/pnpm docker:down(equivalent todocker compose -f infrastructure/docker/docker-compose.yml up -d/down).
Services
| Name | Image | Ports | Volume | Purpose |
|---|---|---|---|---|
postgres |
postgres:16-alpine |
5432 |
pgdata |
Primary database (factory/factory/factory by default) |
redis |
redis:7-alpine |
6379 |
redisdata |
Cache, BullMQ queues, presence |
minio |
minio/minio |
9000 (S3), 9001 (console) |
miniodata |
S3-compatible object storage for file uploads |
minio-init |
minio/mc |
— | — | One-shot: creates factory-uploads bucket (private); exits 0 |
opensearch |
opensearchproject/opensearch:2.15.0 |
9200 |
opensearchdata |
Full-text + permission-filtered search (dev config, see below) |
opensearch-init |
opensearchproject/opensearch:2.15.0 |
— | — | One-shot: puts index template + creates factory-docs index |
api |
built from apps/api/Dockerfile |
4000 |
— | NestJS REST API (profile full only) |
web |
built from apps/web/Dockerfile |
3000 |
— | Next.js front end (profile full only) |
worker |
built from apps/worker/Dockerfile |
— | — | BullMQ workers (profile full only) |
realtime |
built from apps/realtime/Dockerfile |
4001 (ws) |
— | WebSocket realtime server (profile full only) |
All services share the factory-net bridge network. Named volumes are
declared top-level (pgdata, redisdata, miniodata, opensearchdata).
Quick start
# From the repo root:
pnpm docker:up # = docker compose up -d postgres redis minio opensearch
# (+ one-shot minio-init / opensearch-init run and exit)
pnpm install
cp .env.example .env # fill in real secrets
pnpm db:deploy # apply prisma migrations (or db:migrate in dev)
pnpm dev # run apps locally against the containers
# Status / logs:
docker compose -f infrastructure/docker/docker-compose.yml ps
docker compose -f infrastructure/docker/docker-compose.yml logs -f opensearch
# Stop (data is kept in volumes):
pnpm docker:down
# Wipe EVERYTHING (data loss!):
docker compose -f infrastructure/docker/docker-compose.yml down -v
Environment variables
Every variable in docker-compose.yml uses the ${VAR:-default} form, so the
file works without any .env — the defaults mirror .env.example
(factory/factory/factory for Postgres, minioadmin/minioadmin for
MinIO, bucket factory-uploads).
To override: create infrastructure/docker/.env (compose auto-loads it from
its own directory) or export the variables in your shell.
./.env.docker documents all overridable variables — copy it
as a starting point. The app services (profile: full) additionally load the
repo-root .env via env_file (it may be absent — required: false).
Profiles (how the two-tier start works)
| Command | Starts |
|---|---|
pnpm docker:up / compose up -d |
postgres, redis, minio, opensearch (+ one-shot inits) |
docker compose --profile apps up -d |
everything above + api, web, worker, realtime |
The app services are declared with profiles: ["apps"], so the data layer is
the default and the app stack is opt-in. This lets you run the apps on the
host (with pnpm dev, HMR) against the same containers, or run the whole
stack in containers for a clean "it works in Docker" check.
MinIO console
- URL: http://localhost:9001
- Login:
minioadmin/minioadmin(override withMINIO_ROOT_USER/MINIO_ROOT_PASSWORD) - The
factory-uploadsbucket is created private byminio-init(no anonymous access). The API uploads via presigned URLs usingS3_ACCESS_KEY/S3_SECRET_KEYfrom the root.env.
OpenSearch
- Cluster: http://localhost:9200 (plain HTTP — dev only)
- Index:
factory-docs(templatefactory-docs-template), created byopensearch-initfromopensearch/init-indices.json - Cluster health: http://localhost:9200/_cluster/health
Dev-only security posture — READ THIS
opensearch runs with plugins.security.disabled=true and no TLS. This is
intentional for local development and must never be used in production.
Production deployments use managed OpenSearch with security enabled, TLS, and
SEARCH_USERNAME / SEARCH_PASSWORD (see .env.example).
No OpenSearch dashboard
This compose file intentionally does not ship the OpenSearch/Dashboards UI (it is a separate, memory-hungry service). For ad-hoc querying use the REST API:
curl -s 'http://localhost:9200/factory-docs/_search?pretty' \
-H 'Content-Type: application/json' -d '{"query":{"match_all":{}}}'
If you want a GUI locally, pull opensearchproject/opensearch-dashboards:2.15.0
separately and point it at http://localhost:9200.
PostgreSQL
- URL:
postgresql://factory:factory@localhost:5432/factory?schema=public - Extensions installed at first bootstrap (
init/01-extensions.sql, runs only on an emptypgdatavolume):citext,pg_trgm,btree_gist. pgvectoris intentionally not installed yet — it lands with the AI/RAG phase (Phase 12). See the comment ininit/01-extensions.sql.- psql:
docker compose -f infrastructure/docker/docker-compose.yml exec postgres psql -U factory -d factory
Data reset
# Stop containers, keep data:
pnpm docker:down
# Stop and DESTROY all volumes (full reset — migrations run fresh,
# extensions are re-applied, buckets/index recreated):
docker compose -f infrastructure/docker/docker-compose.yml down -v
pnpm docker:up
Note: Postgres init scripts (extensions) run only when the pgdata volume is
empty; down -v guarantees that.
Troubleshooting
| Symptom | Fix |
|---|---|
opensearch container exits with OOM/lock |
bootstrap.memory_lock=true needs ulimits.memlock = -1 (already set). Check host vm.max_map_count >= 262144: sysctl -w vm.max_map_count=262144 |
| Port already in use (5432/6379/9000/9200/…) | Change the left-hand side of the port mapping in docker-compose.yml and update .env URLs. |
App service can't reach postgres/redis |
Ensure you started it with --profile apps; services only resolve by container name on factory-net. |
minio-init exits with error |
Re-run docker compose --profile apps up minio-init after minio is healthy; logs show the mc command output. |