Declare crews, agents, skills, credentials and sidecar services as YAML. Apply converges your workspace toward the manifest — like Terraform, for AI agents.
A manifest is a single YAML file that describes a Crewship workspace as data — agents, skills, credentials, MCP servers, and sidecar containers (Redis, Postgres, etc.). crewship apply --file manifest.yaml converges the live workspace toward the file’s declared state through the same REST API the UI uses; nothing skips RBAC, nothing writes to the database directly.This guide is the narrative tour. For per-field reference, see Configuration → Manifest Schema. For the apply / export CLI flags, see CLI → apply and CLI → export.
--from-env reads ANTHROPIC_API_KEY and GH_TOKEN from the process environment. Skip it (or set --secrets-file env.list) and the credentials are created as PENDING slots that show up in the UI as “Needs value” with a CTA to fill them in.
Re-running apply is idempotent and convergent — the manifest is the source of truth, so resources missing from the live workspace get created, drifted ones updated, and resources that disappeared from the manifest get deleted.
Existing state
Default (sync)
--strict
--replace
Missing
create
create
create
Exists, identical
no-op
error 409
delete + create
Exists, drift
update in place
error 409
delete + create
Workspace has X, manifest doesn’t
delete (prompts)
keep
keep
Destructive operations (delete, replace) always prompt for confirmation unless --yes is passed. The plan is printed first so you can see exactly what will be mutated.
What sync does delete when missing from the manifest:
Manifests never carry secret values. The credentials: block declares slots — env, provider, type, label. Values arrive at apply-time through one of three paths:
# 1. From the process environment (matches credential.env name)crewship apply --file team.yaml --from-env# 2. From a KEY=VALUE file (docker-compose --env-file shape)crewship apply --file team.yaml --secrets-file ./secrets.env# 3. Slot only — value supplied later via UI or `crewship credential set`crewship apply --file team.yaml
The third mode creates credentials with status=PENDING. Agents that need a pending credential fail with credential not configured until the user fills it in; the PENDING sentinel is never injected into the agent’s environment, so the LLM can’t read or exfiltrate it.After apply, the CLI prints the list of pending env vars so you know what’s still left to fill in:
PENDING credentials (set values in the UI, or via 'crewship credential set'): - ANTHROPIC_API_KEY - GH_TOKEN
skills: # Multi-file: SKILL.md in a sibling directory. Recommended for skills # longer than a paragraph. - slug: security-review path: ./skills/security-review/SKILL.md # URL: fetched at apply time. Pin with ref + digest for reproducibility. - slug: git-flow source: https://github.com/anthropics/skills/blob/main/git-flow/SKILL.md ref: v1.2.0 # Inline: SKILL.md body in the manifest itself. Hard cap 8 KB. - slug: house-style inline: | --- name: house-style description: Internal naming conventions license: MIT --- Use named exports only.
Agents reference skills by slug:
agents: - slug: daniel skills: [security-review, house-style]
The validator rejects dangling references (agent points at a skill the manifest doesn’t declare) — no half-applied state.
Declare sidecar containers that run alongside the agent on the crew bridge network. Agents reach them by service name — redis:6379, postgres:5432 — over a private network that’s never published to the host.
spec: devcontainer: image: mcr.microsoft.com/devcontainers/python:3.12 # The agent assembles its connection strings at runtime from # the injected credential (POSTGRES_PASSWORD comes in via the # agent's env_refs below). DATABASE_HOST/PORT/USER/NAME are # literals here; manifest devcontainer.env does NOT expand # ${VAR} substitution — runtime code does. env: DATABASE_HOST: postgres DATABASE_PORT: "5432" DATABASE_USER: postgres DATABASE_NAME: app REDIS_URL: redis://redis:6379 credentials: - env: POSTGRES_PASSWORD provider: NONE type: GENERIC_SECRET services: - name: redis image: redis:7-alpine ports: ["6379"] healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s retries: 5 - name: postgres image: postgres:16 env: POSTGRES_DB: app POSTGRES_USER: postgres env_refs: [POSTGRES_PASSWORD] # injected from the credential vault ports: ["5432"] volumes: - name: pg-data mount: /var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s retries: 10 start_period: 10s
What the provisioner does for each service:
Pull the image (best-effort: tolerates registry outages when a local copy exists).
Create per-crew named volumes (crewship-svc-{crew-slug}-vol-{name}).
Start the container with a DNS alias matching name, on the crew bridge.
Wait for healthcheck.test to report HEALTHY — capped at 60 s across all sidecars in the crew (internal/provider/docker/sidecar.go:EnsureCrewServices).
Only then start the agent runtime.
A failed healthcheck (timeout, non-zero exit) prevents the agent from starting — the error surfaces as sidecar "X" not healthy: .... This is intentional: silently proceeding would mask half-broken setups that look fine until the first DB query times out. Test your healthcheck command locally before committing the manifest.
Services declared without a healthcheck: block aren’t gated — Crewship considers them ready as soon as Docker reports the container running. Use a healthcheck whenever the agent’s first call depends on the service being initialized (Postgres after initdb, Redis after AOF replay, etc.).
Volumes are always named volumes. The validator rejects bind-mount paths so manifests stay portable across machines.
Alternative: in-container databases via devcontainer features
If you prefer the database’s lifecycle to match the agent’s container (a smaller setup, but DB restarts whenever the agent restarts), you can use devcontainer features instead:
The agent reaches both at localhost:5432 and localhost:6379. Works today with zero provisioner changes, but the DB lives inside the same container as the agent.
When you want to ship more than one crew in a single file, switch to kind: Workspace:
apiVersion: crewship/v1kind: Workspacemetadata: name: ACME Engineering slug: acme-engineeringspec: credentials: - { env: ANTHROPIC_API_KEY, provider: ANTHROPIC, type: API_KEY } - { env: GH_TOKEN, provider: GITHUB, type: CLI_TOKEN } skills: - slug: house-style inline: | --- name: house-style description: ACME's code style + commit conventions license: MIT --- Imperative commit messages, max 400 LOC per PR. crews: - slug: code-review name: Code Review agents: - slug: daniel name: Daniel agent_role: LEAD cli_adapter: CLAUDE_CODE skills: [house-style] env_refs: [ANTHROPIC_API_KEY, GH_TOKEN] prompt: | You are Daniel. Apply house-style. - slug: triage name: Triage agents: - slug: alice name: Alice agent_role: LEAD cli_adapter: CLAUDE_CODE skills: [house-style] env_refs: [ANTHROPIC_API_KEY] prompt: | You triage the Linear backlog.
Workspace-scoped credentials and skills are available to every nested crew. A nested crew can override or add to them in its own credentials: / skills: block.
Apply is two-pass: it computes the full plan first, prints it, asks for confirmation on anything destructive, and only then mutates. The flow mirrors terraform plan && terraform apply so you can always see what’s about to change.
$ crewship apply --file acme.workspace.yamlPlan: + credential ANTHROPIC_API_KEY (ANTHROPIC) + credential GH_TOKEN (GITHUB) + crew code-review + crew triage + skill house-style (workspace) + agent code-review/daniel + agent triage/alice ~ agent code-review/petra ← updated, body changed - agent code-review/old-bot ← in workspace but not in manifestPlan includes 1 destructive operation. Continue? [y/N]
--dry-run runs the plan phase only — perfect for code review or CI:
crewship export is the round-trip partner of apply. It pulls the current workspace state and renders it as a manifest — useful for backing up, snapshotting, or migrating a workspace someone else built in the UI.
# One crewcrewship export crew code-review > code-review.crew.yaml# Everything in the active workspacecrewship export workspace > acme.workspace.yaml
The export includes credential slots (without values — they never travel in the file) and skill bodies. Re-applying the exported file on a fresh workspace recreates the same shape:
crewship export crew code-review > backup.yaml# ... later, on a different machinecrewship apply --file backup.yaml --from-env
The hosted JSON Schema endpoint (https://schemas.crewship.ai/v1/manifest.json) is not live yet — it ships in a follow-up release. Until then, validation falls back to the YAML language server’s basic shape checks. The crewship apply --dry-run path is the authoritative validator in the meantime.
Once published, add this line at the top of any manifest and your editor (VS Code, Cursor, JetBrains with the YAML language server) will get autocomplete and inline validation:
# Strict create — fail if anything already exists. Use for "one workspace per PR".crewship apply --file team.yaml --strict --from-env# Idempotent re-apply — converges drifted state, skips identical.crewship apply --file team.yaml --from-env --yes# Dry-run as a PR checkcrewship apply --file team.yaml --dry-run
Pair with secret managers via --secrets-file:
# Pull secrets into a temp file from your vault, then applyop inject -i secrets.tpl.env > /tmp/secrets.envcrewship apply --file team.yaml --secrets-file /tmp/secrets.env --yesrm /tmp/secrets.env