> ## Documentation Index
> Fetch the complete documentation index at: https://docs.crewship.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Auto-managed credentials

> How sidecar database passwords are generated and stored automatically; rotation is available as a follow-up action, not at apply time.

# Auto-managed credentials

When a crew declares a sidecar from a known database image
(`postgres`, `mysql`, `mariadb`, `mongo`, `rabbitmq`,
`elasticsearch`), Crewship generates and manages the sidecar's root
password on your behalf. The operator never sees, types, or rotates
the value — `crewship apply` does it. This page explains how and
why.

## TL;DR

```yaml theme={null}
apiVersion: crewship/v1
kind: Crew
metadata: { name: Backend, slug: backend }
spec:
  services:
    - { name: postgres, image: postgres:16-alpine }
  agents:
    - { slug: lead, name: Lead, agent_role: LEAD }
```

`crewship apply -f` produces:

* One sidecar container (`postgres:16-alpine`) on the crew-private
  bridge network with `POSTGRES_USER=postgres` and a generated
  `POSTGRES_PASSWORD`.
* One credential row in the workspace named `POSTGRES_PASSWORD`,
  status `ACTIVE`, provider `AUTO_MANAGED`, tagged
  `provisioned_for_service=backend/postgres`.
* Every agent in the crew (here: `lead`) gets `POSTGRES_PASSWORD`
  added to its env\_refs automatically.

Zero PENDING credentials. Zero env files. The operator opens the
URL, finishes onboarding, and the crew's database is reachable
from day one.

## What images are recognised

| Image prefix    | Sugar env                         | Auto-credential              |
| --------------- | --------------------------------- | ---------------------------- |
| `postgres`      | `POSTGRES_USER=postgres`          | `POSTGRES_PASSWORD`          |
| `mariadb`       | —                                 | `MARIADB_ROOT_PASSWORD`      |
| `mysql`         | —                                 | `MYSQL_ROOT_PASSWORD`        |
| `mongo`         | `MONGO_INITDB_ROOT_USERNAME=root` | `MONGO_INITDB_ROOT_PASSWORD` |
| `rabbitmq`      | `RABBITMQ_DEFAULT_USER=admin`     | `RABBITMQ_DEFAULT_PASS`      |
| `elasticsearch` | `discovery.type=single-node`      | `ELASTIC_PASSWORD`           |

Images not in the catalog (e.g. `redis`) get no auto-credential at
all — for default Redis the crew-private bridge isolation is enough;
add an explicit `auto_credentials:` block if your image needs auth.

The catalog lives in
[`internal/manifest/known_sidecars.go`](https://github.com/crewship-ai/crewship/blob/main/internal/manifest/known_sidecars.go).
New entries are PR additions, not "any image we recognise" magic —
the behaviour stays auditable.

The image-name match strips registry hosts and tags, so all of
these resolve to the same `postgres` entry:

* `postgres:16-alpine`
* `docker.io/library/postgres:17`
* `harbor.acme.io/library/postgres:16@sha256:...`
* `localhost:5000/postgres:latest`

## Customising the auto-credential

For unknown images or when you want explicit control:

```yaml theme={null}
services:
  - name: cache
    image: ghcr.io/example/redis-with-auth:v1
    auto_credentials:
      - name: REDIS_PASSWORD
        inject_as_env: REDIS_AUTH       # default = name
        inject_to_agents: false          # default = true; here only the
                                         # sidecar sees the value
        length: 48                       # default = 32 bytes (64 hex chars)
        description: Auto-generated AUTH token for the cache sidecar.
```

Operator entries with the same `name` as a sugar default override
the sugar entirely. A crew can also add credentials beyond the
sugar set — both land in the resolved output.

## Threat model — why this is safe

The generated value lives in two places in the workspace database:

1. `credentials.encrypted_value` — AES-256-GCM encrypted with
   `ENCRYPTION_KEY`. Surfaces the row in the UI for audit, rotate
   actions, and "Created by" attribution.
2. `crews.services_json` — plaintext, embedded in the sidecar
   env literal. The docker provider reads this column at sidecar
   start time and passes the value via `docker --env`.

The plaintext-in-services\_json is intentional and bounded:

* Sidecars from `auto_credentials` MUST be crew-private (no host
  port published). The validator refuses `ports:` that publish to
  the host on a service with auto-credentials — that combination
  requires a T2 manual credential and an explicit security review.
* The crew-private bridge network is the **actual** security
  boundary. An attacker who can read the workspace DB also already
  controls bridge isolation and the threat model is "host root,"
  under which a separate encrypted column doesn't help.
* The duplicated value is bounded to the same DB file the
  encrypted column lives in. A backup of the workspace state
  carries both consistently.

A future migration moves the literal value to an encrypted sibling
column once the sidecar env-refs runtime path is wired (`Service.
EnvRefs` resolution at sidecar start). Until then, the duplication
is the trade-off for shipping the friction-zero default today.

## What the UI shows

Auto-managed rows appear under **Credentials** with:

* **Source** column reads "system (backend/postgres)" — `system`
  is the v98 actor type, and the parenthesised slug is the
  `provisioned_for_service` tag. (A future PR pins the row to the
  crew's lead agent and renders "agent: trapper" instead.)
* "Reveal value" and "Edit" actions are hidden.
* "Rotate" is available as a follow-up action (P1).
* Audit timeline shows the create event with the manifest apply
  as the actor user.

## When `auto_credentials` is the WRONG choice

* The sidecar publishes a port to the host (`docker run -p 5432:5432`-
  style). The bridge is no longer the security boundary; declare
  the credential manually under `credentials:` and accept the
  operator-input friction.
* The credential needs to be readable by something outside the
  crew (other crews, an external monitor, a backup job). T1 rows
  are crew-scoped by design — workspace-wide credentials with
  external consumers belong to T2 / T3.
* You want to bring your own value from a secrets manager (Vault,
  AWS Secrets Manager, Doppler). That's the T3 tier — declare a
  credential with a `source:` block instead.

## Migration: existing manifests with `env_refs: [POSTGRES_PASSWORD]`

```yaml theme={null}
# Before (v97 and earlier)
credentials:
  - { env: POSTGRES_PASSWORD, provider: NONE, type: GENERIC_SECRET }

services:
  - name: postgres
    image: postgres:16
    env: { POSTGRES_DB: uo, POSTGRES_USER: postgres }
    env_refs: [POSTGRES_PASSWORD]
```

```yaml theme={null}
# After (v98+)
services:
  - { name: postgres, image: postgres:16, env: { POSTGRES_DB: uo } }
```

Drop the `credentials:` block, drop the `env_refs:`, drop the
explicit `POSTGRES_USER`. Re-apply. Existing user-managed
credential rows are NOT touched — the dispatch refuses to overwrite
a name collision unless `provider=AUTO_MANAGED` was already there.
Migrate by deleting the manual row first if you want the
auto-managed flavour.

## Related

* `SPEC-4` — Auto-managed sidecar credentials (internal design doc). This guide is
  the canonical user-facing reference.
* [`internal/manifest/known_sidecars.go`](https://github.com/crewship-ai/crewship/blob/main/internal/manifest/known_sidecars.go)
  (catalog implementation).
* [Manifests guide](/guides/manifests) (the broader manifest layer).
