skill
This commit is contained in:
parent
c33afaf8f0
commit
06addf396f
1 changed files with 298 additions and 0 deletions
298
SKILL.md
Normal file
298
SKILL.md
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
---
|
||||
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
|
||||
Loading…
Add table
Add a link
Reference in a new issue