Harbor new-service
git clone https://github.com/av/harbor
T=$(mktemp -d) && git clone --depth=1 https://github.com/av/harbor "$T" && mkdir -p ~/.claude/skills && cp -r "$T/.agents/skills/new-service" ~/.claude/skills/av-harbor-new-service && rm -rf "$T"
.agents/skills/new-service/SKILL.mdAdding a New Service to Harbor
Subagent instruction: Before doing any work, read this skill file in full using the
tool at pathread_file. Do not proceed until the file has been read./home/everlier/code/harbor/.agents/skills/new-service/SKILL.md
Harbor is a modular Docker Compose project. Each service is a self-contained unit: a compose file, a service directory, environment variables, app metadata, and documentation. The detailed technical reference lives in
.github/copilot-new-service.md — consult it
for edge cases or patterns not covered here.
Workflow Overview
Gather Info → Scaffold → Compose Config → Env Vars → Metadata → Documentation → Integrations → Test
Every step has validation criteria. Do not advance until the current step passes.
Step 0: Gather Information
Before writing any files, understand what you're building.
Required inputs:
- Service name or GitHub repo URL (from user)
- Docker deployment method: pre-built image, Dockerfile, or docker-compose in repo
- Service category: backend (inference), frontend (web UI), or satellite (tool/utility)
- Ports the service exposes internally
- Persistent data directories (volumes)
- Environment variables the service accepts
Research the repo. Look for:
,docker-compose.yml
, orDockerfile
directorydocker/- README sections on Docker deployment, environment variables, configuration
- The service's default ports and data paths
If the repo has its own docker-compose, study the service definitions — you'll adapt them to Harbor's conventions, not copy them verbatim.
If ambiguous, ask the user. Don't guess the category or deployment method.
Step 1: Select a Handle
The handle is the service's identifier everywhere in Harbor.
Rules:
- Lowercase letters, numbers, hyphens only
- Short, memorable, representative
- Must be unique
Validate:
ls services/compose.${handle}.yml 2>/dev/null && echo "TAKEN" || echo "OK" grep -q "\"${handle}\"\\|'${handle}'\\|${handle}:" app/src/serviceMetadata.ts && echo "TAKEN" || echo "OK"
Step 2: Scaffold
harbor dev scaffold ${handle}
This creates:
— starter compose configservices/compose.${handle}.yml
— service-specific env overridesservices/${handle}/override.env
Step 3: Complete the Compose File
Edit
services/compose.${handle}.yml. The scaffold gives you a skeleton — fill it in.
Mandatory conventions:
- Container name:
${HARBOR_CONTAINER_PREFIX}.${handle} - Main service name in compose MUST match the handle
includes bothenv_file
and./.env./services/${handle}/override.env- Network:
harbor-network - All env vars follow
pattern (HANDLE = uppercase handle)HARBOR_${HANDLE}_* - Port mapping:
${HARBOR_${HANDLE}_HOST_PORT}:${internal_port} - Volume paths:
for config,./services/${handle}/...
for data${HARBOR_${HANDLE}_WORKSPACE}/... - No
policy — automatic restart is not expected in Harborrestart - Image reference:
${HARBOR_${HANDLE}_IMAGE}:${HARBOR_${HANDLE}_VERSION}
If building from source (no pre-built image), use
build instead of image:
build: context: ${HARBOR_${HANDLE}_GIT_REF} dockerfile: Dockerfile
If the service needs multiple containers (e.g., app + database), define them all in the same compose file. The primary container must use the handle as its service name. Auxiliary containers use
${handle}-<role> naming (e.g., windmill-db, windmill-worker).
Example — a typical web service:
services: myservice: container_name: ${HARBOR_CONTAINER_PREFIX}.myservice image: ${HARBOR_MYSERVICE_IMAGE}:${HARBOR_MYSERVICE_VERSION} env_file: - ./.env - ./services/myservice/override.env ports: - ${HARBOR_MYSERVICE_HOST_PORT}:8080 volumes: - ${HARBOR_MYSERVICE_WORKSPACE}/data:/app/data networks: - harbor-network
Step 4: Environment Variables
Add a section to
profiles/default.env for the service.
Port allocation: Read the end of
default.env to find the last used port.
Pick the next available port in the 33000–34999 range. Increment by 10 for services
needing multiple ports.
Required variables:
# Service Name HARBOR_${HANDLE}_HOST_PORT=34XXX HARBOR_${HANDLE}_IMAGE="repo/image" HARBOR_${HANDLE}_VERSION="latest"
Common additional variables:
HARBOR_${HANDLE}_WORKSPACE=./${handle} # persistent data dir HARBOR_${HANDLE}_MODEL=some-default-model # if service uses AI models HARBOR_${HANDLE}_GIT_REF=https://github.com/...#branch # if building from source
Every env var referenced in the compose file must be defined in
.default.env
After editing
default.env, propagate:
harbor config update
Step 5: Service Metadata
Add an entry at the end of the
serviceMetadata object in app/src/serviceMetadata.ts.
${handle}: { name: 'Display Name', tags: [HST.${category}], // + additional tags: HST.cli, HST.api, HST.rag, etc. projectUrl: 'https://github.com/...', wikiUrl: `${wikiUrl}/2.${cat_num}.${next_num}-${Category}-${Name}`, tooltip: 'Brief description for the UI.', },
Logo resolution: After adding the metadata entry, resolve the service logo:
harbor dev add-logos --dry-run # preview harbor dev add-logos # write to serviceMetadata.ts
This populates the
logo field automatically from the project URL.
Category → cat_num mapping:
- Frontend →
2.1 - Backend →
2.2 - Satellite →
2.3
Find the next doc number:
ls docs/2.${cat_num}.*.md | sort -t. -k3 -n | tail -1
Available tags (from
HST enum): backend, frontend, satellite, api, cli,
partial, builtIn, eval, audio, rag, image, workflows, tools, infra.
Add
HST.cli for CLI-only services (blocks "Open" button in the app).
Step 6: Documentation
Create
docs/2.${cat_num}.${next_num}-${Category}-${ServiceName}.md.
Follow the format from
docs/2.3.52-Satellite-Windmill.md — it's the reference example.
Required sections:
### [Service Name](https://github.com/repo-link) > Handle: `${handle}`<br/> > URL: [http://localhost:PORT](http://localhost:PORT) Brief description of what the service does. ## Starting \`\`\`bash harbor pull ${handle} # or harbor build ${handle} harbor up ${handle} --open \`\`\` First-launch notes (default credentials, setup steps, etc.) ## Configuration ### Environment Variables Following options can be set via [`harbor config`](./3.-Harbor-CLI-Reference.md#harbor-config): \`\`\`bash # Document every HARBOR_${HANDLE}_* variable with descriptions \`\`\` ### Volumes Describe persistent data and configuration mounts. ## Troubleshooting \`\`\`bash harbor logs ${handle} \`\`\` Common issues and solutions. ## Links - [Official Documentation](...) - [GitHub Repository](...)
Rules:
- Reference Harbor CLI commands, not raw Docker commands
- Document ALL env vars from
default.env - No behavior should surprise the user — if you add it, document it
Step 7: Service Directory
Create
services/${handle}/.gitignore for persistent data directories:
data/ cache/ logs/
Add any config files, entrypoints, or Dockerfiles the service needs into
services/${handle}/.
Step 8: Cross-Service Integration (If Needed)
Cross-files are applied when multiple services run together. They live in
services/ with
the naming pattern compose.x.${handle}.${other}.yml.
When to create cross-files:
| Integration | File | Purpose |
|---|---|---|
| Ollama | | Set Ollama URL env vars, add |
| GPU | | GPU passthrough via deploy.resources |
| Traefik | | Reverse proxy labels |
Ollama integration pattern:
services: ${handle}: depends_on: - ollama environment: - OLLAMA_API_BASE=${HARBOR_OLLAMA_INTERNAL_URL}
Check the service's docs for the correct Ollama env var name. Common variants:
OLLAMA_API_BASE, OLLAMA_URL, OLLAMA_BASE_URL, OLLAMA_HOST.
Step 9: Test
Build/pull first:
harbor pull ${handle} # pre-built image harbor build ${handle} # source build
Start and verify:
harbor up ${handle} harbor logs ${handle}
Check for:
- Service starts without errors in logs
- Port is accessible:
orcurl -s http://localhost:${PORT}harbor open ${handle} - If it has an API, make a test request
- If it depends on other services, test with them running
Clean up after testing:
harbor down ${handle}
Validation Checklist
Before declaring the service complete, verify all of these:
- Handle is unique and valid
- Compose file follows all conventions (naming, env_file, network, no restart)
- All env vars in compose are defined in
default.env -
ran after editingharbor config updatedefault.env - Metadata entry in
with correct category and doc linkserviceMetadata.ts - Documentation created with all required sections
-
in service directory covers generated files.gitignore - Service starts successfully (
)harbor up ${handle} - Logs show healthy startup (
)harbor logs ${handle} - Cross-files created for relevant integrations
-
ran to resolve the service logo (or added manually)harbor dev add-logos
Common Pitfalls
- Forgetting
after editingharbor config update
— yourdefault.env
won't have the new variables and the service will fail with empty substitutions..env - Wrong volume paths — must use
(relative to repo root), not absolute paths../services/${handle}/... - Setting
— Harbor doesn't expect auto-restart; omit the policy.restart: always - Editing
directly — always use.env
or editharbor config set
+default.env
.harbor config update - Mismatched service name — the primary compose service name must equal the handle.