Projects

ProjectInfrastructure · 2026

Infra Lab

Ready-to-use Docker Compose environments for databases, queues, search, observability, caches, and vector stores.

  • Docker Compose
  • Databases
  • Queues
  • Search
  • Observability
 cd databases/postgresql && docker compose up -d[+] Running 2/2  network created, postgres started docker compose pspostgres  Up 3 seconds  0.0.0.0:5432->5432/tcp cd ../../search-engines/typesense && docker compose up -d[+] Running 1/1  typesense started on 0.0.0.0:8108

Infrastructure · Project

Chapter
Templates and infrastructure
Year
Availability
Open source
Last commit

Infra Lab is a repository of Docker Compose setups I keep so that a local service is one command away. Each tool gets its own directory with a docker-compose.yml and a short README describing ports and credentials. It exists because I kept writing the same compose file from memory whenever a project needed a database to talk to.

The alternative most people reach for is one large stack that starts everything at once. That works until you want a single Redis and end up waiting on Kafka. Or until a version pin in the shared file breaks a project that was fine yesterday. A throwaway environment should not carry the weight of the environments next to it.

One directory per tool

Directories are grouped by role rather than by project: caches/, ci-cd/, databases/, messaging-queues/, observability/, reverse-proxies/, search-engines/ and vector-databases/. Inside each sits one directory per tool, holding a compose file and a README. So databases/postgresql, messaging-queues/kafka, observability/grafana-prometheus, search-engines/typesense and vector-databases/qdrant. Nothing is shared between them. There is no root compose file, no common network and no .env at the top of the tree. The README beside a compose file carries the ports and the credentials, which is why the compose file is never the only documentation.

Inside a compose file

Every image tag is an exact version rather than latest: postgres:18.4, typesense/typesense:30.2, grafana/grafana:13.0.2, confluentinc/cp-kafka:8.2.1. Several stacks bring a companion UI along, so PostgreSQL starts pgAdmin and Redis starts RedisInsight beside the service itself. Kafka runs in KRaft mode with no ZooKeeper, advertising two listeners so that a container reaches the broker at kafka:29092 while the host reaches it at localhost:9092. The PostgreSQL file carries a comment worth keeping. Postgres 18 stores data in a versioned subdirectory, so the volume has to mount the parent path or the container never starts.

ServiceHost ports
PostgreSQL with pgAdmin5432, 8432
Redis with RedisInsight6379, 8001
Kafka broker and JMX9092, 9101
Grafana with Prometheus3000, 9090
Typesense and Meilisearch8108, 7700
Qdrant and ChromaDB6333, 8000

Choices and their cost

  • One tool per directory, with nothing shared. Starting a database never starts a queue, and a bad pin in one file cannot reach another. The cost is that two of these cannot talk to each other without manual network work, since there is no shared compose project to join.
  • Tags are pinned rather than floating. A setup that worked last month still works today, and an upgrade is a visible edit. The cost is that pins go stale, and refreshing them is manual across the whole catalog.
  • Credentials sit in plain text in the compose file and again in the README. For a throwaway environment that is the point, because a password you have to go and look up defeats the purpose. It also means nothing here belongs on a shared host.
  • Data lives in named volumes, so docker compose down keeps it and down -v removes it. Typesense is the exception. It bind mounts ./data into the repository directory, which is the inconsistency I would fix first.

Bringing one up

cd databases/postgresql && docker compose up -d
docker exec -it PostgresCont psql -U postgres
curl -sI localhost:8432 | head -1
cd ../../search-engines/typesense && docker compose up -d
curl -H "X-TYPESENSE-API-KEY: xyz" localhost:8108/health
cd ../../messaging-queues/kafka && docker compose -f docker-compose.yaml up -d
docker compose down -v

docker compose up -d is the entire interface, and stopping a service leaves nothing behind. Two details catch people out. In pgAdmin the server host is postgres_db rather than localhost, because the two containers share the compose network and talk by service name. Kafka needs the explicit -f, because its file is named docker-compose.yaml while most of the catalog uses .yml.

Rough edges

Naming is not uniform. Kafka and ChromaDB use docker-compose.yaml, the ChromaDB directory is spelled chrome_db, and its README is Readme.md. Only ChromaDB and Nginx Proxy Manager declare a healthcheck. Ports collide if you run neighbours together, since MySQL and MariaDB both publish 3306, and ClickHouse and SonarQube both want 9000. The RedisInsight container carries a placeholder password rather than the real Redis one. There are no resource limits and no TLS anywhere, because none of this is meant to be exposed. Adding a tool means adding a directory, which is why the catalog can grow without any of it becoming load bearing. It is MIT licensed.

Next projectTemplates

FastAPI Boilerplate

View project