298 lines
12 KiB
Markdown
298 lines
12 KiB
Markdown
---
|
|
name: homelab-docker-compose
|
|
description: Creates compose files and .env.example for Robbie's homelab (Unraid, FireEye NX4500). Follows curated_compose conventions with stack-specific networks, shared external pipeline network + aliases, bundled infra, Venice.ai for OpenAI, and optional/commented SWAG external network.
|
|
---
|
|
|
|
# Homelab Docker Compose Skill
|
|
|
|
Use this skill when Robbie asks you to create a docker-compose stack for his self-hosted homelab. This skill encodes all of his preferences, system constraints, and conventions.
|
|
|
|
## System Context
|
|
|
|
- **Server**: FireEye NX4500 repurposed for homelab
|
|
- **CPU**: 2x E5-2620 v4 (2x E5-2699 v4 coming soon)
|
|
- **RAM**: 126GB
|
|
- **GPU**: No GPU currently installed (2x T4 Tesla planned for future)
|
|
- **OS**: Unraid
|
|
- **Orchestration**: Docker Compose + Dockhand (`.env` files and compose files are deployed via Dockhand)
|
|
- **Shared cross-stack network**: External Docker network `pipeline` used for inter-stack service connectivity (Headroom, Chroma, Langfuse, Dify, OTEL-LGTM, etc.)
|
|
- **Compose storage**: Stacks live in `/mnt/user/robbie/git/curated_compose/<stack-name>/` with a companion `.env.example` in the same directory
|
|
|
|
## Conventions for Every Compose
|
|
|
|
### 1. Stack-specific Network
|
|
|
|
Every stack MUST have its own bridge network so services can communicate by hostname. Name the network after the stack (e.g., `mystack`). Every service in the stack joins this network.
|
|
|
|
```yaml
|
|
networks:
|
|
mystack:
|
|
name: mystack
|
|
driver: bridge
|
|
```
|
|
|
|
### 2. External Networks
|
|
|
|
#### 2a. Shared Pipeline Network (cross-stack service-to-service)
|
|
|
|
Use the external `pipeline` network for services that must be reachable by other stacks (e.g., shared APIs, vector DBs, observability endpoints, proxy endpoints).
|
|
|
|
Declare it at the bottom and attach only the services that need cross-stack access.
|
|
|
|
```yaml
|
|
networks:
|
|
mystack:
|
|
name: mystack
|
|
driver: bridge
|
|
pipeline:
|
|
name: pipeline
|
|
external: true
|
|
```
|
|
|
|
For services on `pipeline`, use explicit aliases so other stacks can resolve stable hostnames.
|
|
|
|
```yaml
|
|
services:
|
|
api:
|
|
networks:
|
|
mystack: {}
|
|
pipeline:
|
|
aliases:
|
|
- mystack-api
|
|
```
|
|
|
|
Do **not** attach internal-only services (databases, caches, workers) to `pipeline` unless cross-stack access is explicitly required.
|
|
|
|
##### Network Attach Matrix (canonical)
|
|
|
|
Use this decision table when wiring `networks:` for each service:
|
|
|
|
| Service type | Attach to stack network | Attach to `pipeline` | Attach to `swag` |
|
|
| -------------------------------------------------------------------------------- | ----------------------- | -------------------- | ----------------------------- |
|
|
| Internal-only infra (Postgres, Redis, RabbitMQ, workers) | Yes | No (default) | No |
|
|
| Cross-stack private service (vector DB, proxy, telemetry endpoint, internal API) | Yes | Yes (with alias) | No (default) |
|
|
| Public web/API behind reverse proxy | Yes | Usually yes | Optional/commented by default |
|
|
|
|
#### 2b. OTEL-LGTM Reserved Pipeline Aliases
|
|
|
|
For the OTEL-LGTM stack, reserve these **pipeline aliases** on the telemetry ingress service:
|
|
|
|
- `otel`
|
|
- `lgtm`
|
|
|
|
This ensures every other stack can use stable hostnames for telemetry configuration.
|
|
|
|
```yaml
|
|
services:
|
|
otel-lgtm-otelcol:
|
|
networks:
|
|
otel-lgtm: {}
|
|
pipeline:
|
|
aliases:
|
|
- otel
|
|
- lgtm
|
|
```
|
|
|
|
Recommended cross-stack telemetry endpoints over `pipeline`:
|
|
|
|
- OTLP gRPC: `otel:4317`
|
|
- OTLP HTTP: `http://otel:4318`
|
|
|
|
#### 2c. SWAG Network (reverse proxy)
|
|
|
|
SWAG remains optional. If a stack needs SWAG reverse-proxy access, include SWAG as a second external network declaration at the bottom, but keep it **COMMENTED OUT by default**.
|
|
|
|
```yaml
|
|
networks:
|
|
mystack:
|
|
name: mystack
|
|
driver: bridge
|
|
pipeline:
|
|
name: pipeline
|
|
external: true
|
|
# swag:
|
|
# name: swag
|
|
# external: true
|
|
```
|
|
|
|
If a service needs SWAG exposure (web/API), add SWAG in that service's `networks:` section, also commented out by default.
|
|
|
|
```yaml
|
|
services:
|
|
nginx:
|
|
networks:
|
|
- mystack
|
|
- pipeline
|
|
# - swag
|
|
```
|
|
|
|
Do NOT assume public exposure by default. Only SWAG-enable services that realistically need reverse-proxying (web frontends, APIs), not internal-only services.
|
|
|
|
### 3. Bundled Infrastructure
|
|
|
|
If a stack requires PostgreSQL, Redis, RabbitMQ, or any other infrastructure dependency, include it **within the same compose file**. Do not share databases, caches, or message brokers between stacks unless Robbie explicitly instructs otherwise.
|
|
|
|
Each infrastructure service should:
|
|
|
|
- Use a well-known, official image (e.g., `postgres:15-alpine`, `redis:7-alpine`)
|
|
- Set `restart: unless-stopped`
|
|
- Include a `healthcheck`
|
|
- Use a **relative bind mount** with `./<service>-data` for persistent data
|
|
- Join the stack's internal network
|
|
- Accept configuration via `${VAR}` environment variables from the `.env` file
|
|
|
|
Example pattern:
|
|
|
|
```yaml
|
|
services:
|
|
prowler-db:
|
|
image: postgres:15-alpine
|
|
restart: unless-stopped
|
|
environment:
|
|
POSTGRES_USER: ${DB_USERNAME}
|
|
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
|
POSTGRES_DB: ${DB_DATABASE}
|
|
volumes:
|
|
- ./prowler-db-data:/var/lib/postgresql/data
|
|
healthcheck:
|
|
test:
|
|
[
|
|
"CMD",
|
|
"pg_isready",
|
|
"-h",
|
|
"prowler-db",
|
|
"-U",
|
|
"${DB_USERNAME:-postgres}",
|
|
]
|
|
interval: 5s
|
|
timeout: 3s
|
|
retries: 30
|
|
networks:
|
|
- prowler
|
|
```
|
|
|
|
### 3a. Persistent Storage (Relative Bind Mounts)
|
|
|
|
All persistent data uses **relative bind mounts** with the convention `./<service>-data`. This creates a directory alongside the compose file in `curated_compose/<stack>/`, keeping data co-located and easy to find.
|
|
|
|
Examples:
|
|
|
|
| Service | Mount path | Creates directory |
|
|
| -------------------------- | -------------------------------------------- | --------------------------------------------- |
|
|
| PostgreSQL for `prowler` | `./prowler-db-data:/var/lib/postgresql/data` | `curated_compose/prowler/prowler-db-data/` |
|
|
| Redis for `prowler` | `./prowler-redis-data:/data` | `curated_compose/prowler/prowler-redis-data/` |
|
|
| File storage for `prowler` | `./prowler-storage:/app/storage` | `curated_compose/prowler/prowler-storage/` |
|
|
| Chroma vector DB | `./data:/chroma/chroma/` | `curated_compose/chroma/data/` |
|
|
|
|
For supporting config files (e.g., nginx configs, custom scripts), also use relative bind mounts like `./nginx/nginx.conf:/etc/nginx/nginx.conf`.
|
|
|
|
These directories should be added to `.gitignore` at the `curated_compose` level so persistent data is not committed to version control.
|
|
|
|
### 4. OpenAI / LLM Configuration
|
|
|
|
If a stack calls for OpenAI or an LLM provider, **always configure it to use an OpenAI-compatible endpoint** pointing to Venice.ai:
|
|
|
|
- **Base URL**: `https://api.venice.ai/api/v1`
|
|
- **API Key**: `${VENICE_API_KEY}` (Robbie will supply this in his `.env`)
|
|
|
|
If the application supports an `OPENAI_API_BASE` / `OPENAI_API_KEY` environment variable pattern, use that:
|
|
|
|
```yaml
|
|
OPENAI_API_KEY: ${VENICE_API_KEY}
|
|
OPENAI_API_BASE: https://api.venice.ai/api/v1
|
|
```
|
|
|
|
If the application does **NOT** support OpenAI-compatible endpoints (i.e., it requires a direct OpenAI API key or uses a non-standard API format), **alert Robbie immediately** before proceeding. Do not attempt to patch, proxy, or work around this without his explicit approval.
|
|
|
|
### 5. Well-maintained Projects Only
|
|
|
|
Prefer actively maintained, official, or widely-adopted images and projects. Do not suggest unmaintained forks, obscure repositories, or projects with low community trust.
|
|
|
|
### 6. No Patching or Custom Builds Without Approval
|
|
|
|
Do NOT modify a service's code, create a Dockerfile patch, or build a custom container unless **both** of these are true:
|
|
|
|
1. The patch has been tested and confirmed working by Robbie in the past
|
|
2. The patch is a simple injection or small configuration overlay
|
|
|
|
If neither condition is met, ask Robbie for approval first.
|
|
|
|
### 7. Naming Conventions
|
|
|
|
Service names (and thus hostnames) MUST be descriptive and unmistakable about what they are. The pattern is:
|
|
|
|
- **Standalone service** (the service IS the stack): Use the service name directly.
|
|
- Example: A Chroma server → `chroma`
|
|
- Example: A Qdrant server → `qdrant`
|
|
|
|
- **Multi-service stack**: Prefix every service name with the stack name, then append the role.
|
|
- Example: Prowler frontend → `prowler-web`
|
|
- Example: Prowler API backend → `prowler-api`
|
|
- Example: Prowler PostgreSQL → `prowler-db`
|
|
- Example: Prowler Redis → `prowler-redis`
|
|
- Example: Prowler worker → `prowler-worker`
|
|
|
|
This makes it trivially obvious which stack a service belongs to and what its role is just from the hostname.
|
|
|
|
- **Network**: `mystack` (named after the stack directory)
|
|
- **Volumes**: Relative bind mounts with `./<service>-data` pattern — see section 3a
|
|
- **Container names**: Do NOT set explicit `container_name:` — let Docker generate them. Dockhand handles identification.
|
|
|
|
### 8. Volume Declarations
|
|
|
|
Since persistent data uses relative bind mounts (`./<service>-data`), the `volumes:` section at the bottom of the compose file is typically **empty** or omitted entirely. Only add named volumes here if a service specifically requires one (e.g., for ephemeral caches or Docker-specific features).
|
|
|
|
```yaml
|
|
volumes: {}
|
|
```
|
|
|
|
### 9. Port Exposure
|
|
|
|
Only expose ports on services that need external access (a web UI, an API that other machines on the network hit, etc.).
|
|
|
|
If a service is **internal-only** (a database, cache, worker, or any service that is only accessed by other services within the same stack network), do **NOT** expose its ports. Exposing ports on internal services creates port conflicts when multiple stacks use the same database or cache image on the same host.
|
|
|
|
Examples of when to expose ports:
|
|
|
|
- A web frontend that needs to be accessed via the host IP: expose port 80/443
|
|
- An API that other stacks or external tools hit directly: expose its port
|
|
|
|
Examples of when NOT to expose ports:
|
|
|
|
- PostgreSQL, Redis, RabbitMQ — these are accessed by other services in the stack via the internal network hostname, not via `localhost:PORT`
|
|
- Internal workers, background job processors, or sidecars
|
|
|
|
When a port **is** exposed, always pull the host port from an environment variable with a sensible default:
|
|
|
|
```yaml
|
|
ports:
|
|
- ${EXPOSE_HTTP_PORT:-80}:80
|
|
```
|
|
|
|
### 10. Environment Variables via `.env`
|
|
|
|
Every configurable value should be pulled from the environment via `${VAR}` or `${VAR:-default}`. Create a companion `.env.example` file that:
|
|
|
|
- Contains **all non-sensitive** configuration variables with sensible defaults
|
|
- Uses placeholder values for secrets (e.g., `your-secure-password-here`, `change-me`)
|
|
- Does NOT contain real passwords, API keys, or tokens
|
|
- Is well-commented so Robbie knows what each value does
|
|
- The actual `.env` file is deployed by Dockhand and should NOT be committed
|
|
|
|
## General Structure
|
|
|
|
```
|
|
curated_compose/<stack-name>/
|
|
├── compose.yaml
|
|
├── .env.example
|
|
└── <any supporting config dirs or files>
|
|
```
|
|
|
|
## When Robbie Gives You a Stack Request
|
|
|
|
1. Research the project to find its recommended Docker Compose configuration
|
|
2. Adapt it to Robbie's conventions (stack network, `pipeline` aliases, bundled infra, Venice.ai, optional/commented SWAG, etc.)
|
|
3. Write `compose.yaml` in the appropriate `curated_compose/<name>/` directory
|
|
4. Create `.env.example` with all configurable variables documented
|
|
5. If the stack requests OpenAI and does NOT support openai-compatible endpoints, alert Robbie
|
|
6. If you're unsure about any preference, ask Robbie — do not guess
|