English | 한국어
A collection of Docker Compose files for the backing services I run on my development machine: search, a relational database, a vector store, distributed tracing, a static-site dev server, and a containerized Terraform CLI.
There is no application code here. Each file is a self-contained stack that can be started on its own, and all of them attach to one shared Docker bridge network so containers from different stacks can reach each other by service name.
| Stack | Compose file | Services | Published ports |
|---|---|---|---|
| Elasticsearch 8 | elasticsearch/elkstack-v1.2.yml |
Elasticsearch 8.17.2 (Nori analyzer, built locally), one-shot user bootstrap, Kibana 8.17.2 | 10041, 10042 |
| Elasticsearch 7 | elasticsearch/elasticsearch-7-14-1/elkstack-v1.2.yml |
Same shape on 7.14.1 | 10060, 10061 |
| Kibana → remote | elasticsearch/kibana-ifns.yml |
Kibana 8.17.2 only, pointed at a cluster elsewhere on the network | 10040 |
| ChromaDB | chromadb/chromadb-v10.yml |
ChromaDB, OpenTelemetry Collector, Zipkin | 10050, 10051 |
| PostgreSQL | postgresql/postgresql-v11.yml |
PostgreSQL 17, schema loaded from init.sql on first boot |
10010 |
| PostgreSQL + pgAdmin | postgresql/postgresql-pgadmin-v10.yml |
PostgreSQL 17, pgAdmin 4 | 10010, 10011 |
| Jekyll | jekyll/jekyll.yml |
Jekyll dev server with --watch |
10020 |
| Terraform | terraform/terraform.yml |
Terraform 1.9 CLI, run as a one-off command | — |
The two PostgreSQL files are alternatives, not companions — both publish 10010, so only one runs at a time.
-
Docker Engine with Compose v2 (
docker compose, notdocker-compose) -
The shared network, created once per machine:
docker network create avocado-network
Every stack declares this network as
external: trueand will fail to start if it does not exist. -
Enough RAM for the Elasticsearch stacks:
ES_HEAP_SIZEdefaults to3gper node, plus container overhead.
Each stack directory has its own .env.example. Copy it, fill in the values, then bring the stack up from inside that directory.
# Elasticsearch 8 + Kibana (builds a local image on first run)
cd elasticsearch
cp .env.example .env
docker compose -f elkstack-v1.2.yml up -d --build
# Elasticsearch → http://localhost:10041 (user: elastic)
# Kibana → http://localhost:10042# PostgreSQL
cd postgresql
cp .env.example .env
docker compose -f postgresql-v11.yml up -d# ChromaDB + tracing
cd chromadb
cp .env.example .env
docker compose -f chromadb-v10.yml up -d
# ChromaDB → http://localhost:10050
# Zipkin → http://localhost:10051# Terraform (no long-running container)
cd terraform
cp .env.example .env # TF_PROJECT must be an absolute host path
docker compose -f terraform.yml run --rm terraform init
docker compose -f terraform.yml run --rm terraform planTear a stack down with docker compose -f <file> down, or down -v to drop its named volumes as well.
Ports. Nothing binds a service's default port, so these stacks do not collide with a PostgreSQL or Elasticsearch already installed on the host. Each area owns a band:
| Band | Area |
|---|---|
1001x |
Relational database and its admin UI |
1002x |
Web / static site |
1004x |
Elastic 8.x, and Kibana against a remote cluster |
1005x |
Vector store and tracing |
1006x |
Elastic 7.x |
Configuration. Credentials and host paths come from a per-directory .env, which is gitignored. Required variables use the ${VAR:?message} form, so a missing value stops the stack at startup with a named error instead of falling back to a default. .env.example files are committed and document what each stack needs.
Image versions. Most images are pinned to explicit tags rather than latest. The exceptions are dpage/pgadmin4:latest, jekyll/jekyll:latest, and openzipkin/zipkin (untagged, so also latest) — all three are UI or tooling containers holding no data worth reproducing. The ChromaDB tag (1.5.8.dev23) is a pre-release build.
Locally built images. The Nori-enabled Elasticsearch images are tagged elasticsearch-nori:<version> — deliberately not the upstream tag, so Docker cannot resolve the official plugin-less image in their place. These stacks need --build on first run.
Startup order. Where order matters it is expressed with health checks rather than delays: the Kibana service waits for the bootstrap container's service_completed_successfully, which in turn waits for Elasticsearch's service_healthy. The pgAdmin service waits on PostgreSQL's pg_isready health check.
State. PostgreSQL, pgAdmin, and Elasticsearch persist to named Docker volumes. ChromaDB and Jekyll use bind mounts driven by CHROMA_DATA_DIR and JEKYLL_SITE_DIR, defaulting to a path relative to the compose file.
Line endings. .gitattributes normalizes text files to LF and marks certificate and image files binary, so checkouts on Windows do not show the tree as modified.
Dockerfile.es-nori installs the analysis-nori Korean morphological analyzer into the official image at build time, skipping the install if the plugin is already present.
Both Elastic stacks run discovery.type=single-node with security enabled and TLS switched off on the HTTP and transport layers. The health check treats Elasticsearch's missing authentication credentials response as the success signal — an unauthenticated probe being rejected shows the node is both serving and enforcing security.
A one-shot elasticsearch_settings container sets the kibana_system password over the security API and exits; Kibana starts only after it completes successfully.
The 8.17.2 and 7.14.1 stacks use different ports, container names, and volumes, so they can run at the same time. The 7.x directory has no .env.example of its own — copy the parent one:
cd elasticsearch/elasticsearch-7-14-1
cp ../.env.example .env
docker compose -f elkstack-v1.2.yml up -d --buildkibana-ifns.yml starts Kibana alone against REMOTE_ES_HOST, for browsing a cluster this repo does not manage.
Two files in elasticsearch/ are reference material and are not used by any stack here:
config/elasticsearch.yml— a TLS-enabled node configuration, including theelasticsearch-certutiland keystore commands needed to produce it. Certificate material belongs incerts/, which is gitignored and generated locally.logstash/config/logstash.yml— pipeline settings from an earlier Logstash setup. No compose file starts Logstash.
ChromaDB is configured to persist to /data and to export OTLP traces to the OpenTelemetry Collector, which batches them under a memory limiter and exports to Zipkin at http://zipkin:9411/api/v2/spans. The collector reads otel-collector-config.yaml, mounted read-only relative to the compose file. ANONYMIZED_TELEMETRY is left on.
Both files set the user, password, and database from .env. Only postgresql-v11.yml mounts init.sql, and Postgres runs it only when the data directory is empty — so it applies on a first boot, or after down -v.
init.sql creates a rag_chatbot schema with a config key/value table and seeds it with retrieval settings, index names, and model paths for an application that reads its configuration from the database. Secret-looking values in it are placeholders (__SET_AT_DEPLOY__); real credentials are injected at application start.
Serves the directory named by JEKYLL_SITE_DIR (default ./jekyll-site, a minimal sample site) with jekyll serve --watch --drafts. On Windows and WSL bind mounts, inotify events are not always delivered — add --force_polling to the command if edits are not picked up.
Runs the pinned CLI in a container instead of installing it on the host, with TF_PROJECT mounted at /workspace. Docker cannot resolve relative paths in bind mounts, so TF_PROJECT must be absolute. AWS credentials pass through if set and default to empty, which is fine for local-only providers; AWS_REGION defaults to ap-northeast-2.
.
├── chromadb/
│ ├── chromadb-v10.yml # Chroma + OTel Collector + Zipkin
│ └── otel-collector-config.yaml # OTLP receive → batch → Zipkin export
├── elasticsearch/
│ ├── elkstack-v1.2.yml # ES 8.17.2 + Nori + Kibana
│ ├── Dockerfile.es-nori # Nori installed at image build time
│ ├── kibana-ifns.yml # Kibana → remote cluster
│ ├── config/elasticsearch.yml # TLS reference config (unused by the stacks)
│ ├── logstash/config/ # Logstash settings (unused by the stacks)
│ └── elasticsearch-7-14-1/ # Parallel 7.14.1 stack
├── postgresql/
│ ├── postgresql-v11.yml # Postgres 17 + init.sql
│ ├── postgresql-pgadmin-v10.yml # Postgres 17 + pgAdmin
│ └── init.sql # Schema and config seed, first boot only
├── jekyll/
│ ├── jekyll.yml # Jekyll dev server
│ └── jekyll-site/ # Sample site to serve
├── terraform/terraform.yml # Containerized Terraform CLI
├── .gitattributes # LF normalization, linguist hints
└── prompt-documentation.md # Working notes on changes to this repo
- This is a local development setup. It is not hardened for production: HTTP-layer TLS is off in the compose stacks, containers run as their images' default users, there are no CPU or memory limits, and secrets live in
.envrather than a secret manager. - Written and used on Windows with Docker Desktop. The compose files contain no OS-specific paths and should run unchanged on Linux and macOS.
bootstrap.memory_lock=truedepends on thememlockulimits already declared in the Elastic stacks; removing them will cause the node to fail at startup.- The two Elasticsearch stacks read the same variable names from separate
.envfiles. Their ports, container names, and volumes differ, so running both is safe, but the passwords are whatever each.envsays.