# Docker Local Environment

## Prerequisites

| Requirement        | How to get it                                               |
| ------------------ | ----------------------------------------------------------- |
| Docker Desktop     | [Install](https://www.docker.com/products/docker-desktop/)  |
| AWS credentials    | `aws sso login` (or `awsume orchard-dev`)                   |
| `GITHUB_NPM_TOKEN` | Set in your shell profile (needed for private npm packages) |
| ECR login          | `pnpm docker:login`                                         |
| `apps/server/.env` | Copy from `apps/server/.env.shadow`, fill in secrets        |
| `apps/client/.env` | Copy from `apps/client/.env.shadow`, fill in secrets        |
| Snowflake key      | Place at `~/.ssh/snowflake/rsa_key.p8`                      |

> **Note:** `CODA_DB_IDENTITY_HMAC_SECRET` and `CODA_DB_IDENTITY_AES_KEY` must be set in `apps/server/.env` for DB persistence. Generate test values:
>
> ```bash
> node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
> ```

## Quick Start

```bash
pnpm docker:up
```

Starts: MySQL + Redis + Prisma migrations + Express server (tsx --watch) + Vite dev server (HMR).

- **Client (HMR):** http://localhost:6005
- **Server API:** http://localhost:8080

## Commands

| Command                        | What it does                                                |
| ------------------------------ | ----------------------------------------------------------- |
| `pnpm docker:up`               | Dev mode - server + Vite HMR                                |
| `pnpm docker:up:preview`       | Preview mode - built client served by Express + local infra |
| `pnpm docker:down`             | Stop dev containers                                         |
| `pnpm docker:logs`             | Tail logs from dev services                                 |
| `pnpm docker:clean`            | Stop dev + remove images, volumes, orphans                  |
| `pnpm docker:test:unit`        | Lint + typecheck + unit tests (no infra)                    |
| `pnpm docker:test:functional`  | Functional tests with real DB + Redis                       |
| `pnpm docker:test:integration` | Integration tests against running server                    |
| `pnpm docker:test:e2e`         | E2E browser tests against full stack (not yet implemented)  |
| `pnpm docker:test:down`        | Stop test containers                                        |
| `pnpm docker:test:clean`       | Stop test + remove images, volumes, orphans                 |

> **Isolation:** Each test type uses its own Docker Compose project (`coda-test-unit`, `coda-test-functional`, `coda-test-integration`), separate from the dev project (`coda`). This means `docker:up`, `docker:test:functional`, and `docker:test:integration` can all run simultaneously without port conflicts or shared databases. Use `docker:test:down` / `docker:test:clean` to manage all test containers.

## Architecture

### Default (dev mode)

```
docker compose --profile dev up
```

```mermaid
graph LR
    mysql["mysql:3306"] --> migrate["migrate (one-shot)"]
    redis["redis:6379"] --> server["server:8080"]
    s3["s3:9000"] --> server
    migrate --> server
    server --> client["client-dev:5173<br/>(host :6005)"]
```

- **server** uses `tsx --watch` with bind-mounts for `apps/server/src`, `packages/core-api/src`, `packages/db/src` (live reload)
- **client-dev** uses Vite HMR with bind-mount for `apps/client/src` (instant browser updates)
- **mysql** uses `tmpfs` (RAM-backed, ephemeral) with performance tuning
- **migrate** runs `prisma migrate deploy` and exits

### Preview mode (local production)

```
docker compose --profile preview up
```

```mermaid
graph LR
    mysql["mysql:3306"] --> migrate["migrate (one-shot)"]
    redis["redis:6379"] --> preview["preview:8080<br/>(host :6005)"]
    s3["s3:9000"] --> preview
    migrate --> preview
```

Visit http://localhost:6005. Built client is served by Express (same as production). Port 6005 matches the dev profile for consistency.

> **Note:** Preview mode bakes Auth0 and Sentry config into the client at build time. Set these in your shell or a root `.env` file (Docker Compose auto-loads it):
>
> ```bash
> APP_URL=http://localhost:6005
> AUTH0_DOMAIN=your-tenant.auth0.com
> AUTH0_CLIENT_ID=your-client-id
> AUTH0_AUDIENCE=your-audience
> SENTRY_DSN=              # optional
> ```
>
> Without these, the client builds with empty Auth0 config and authentication won't work.

### Production profile (CI/Fargate only)

> **Do not use locally.** The `production` profile starts only the `production` service with no local infrastructure (no MySQL, Redis, or S3). It expects AWS-managed services (Aurora, ElastiCache, S3) and will fail to connect if run on a dev machine. Use `preview` instead for local production builds.

## Testing

### In Docker (CI-style, runs all tests in category)

```bash
pnpm docker:test:unit           # lint + typecheck + unit (no infra)
pnpm docker:test:functional     # with real DB + Redis
pnpm docker:test:integration    # HTTP against running server
pnpm docker:test:e2e            # not yet implemented — needs Playwright Dockerfile target
```

Exit codes propagate correctly for CI pipelines.

### From Host (targeted, faster iteration)

**Unit tests** - no Docker needed:

```bash
pnpm test:unit                                    # all packages
pnpm --filter @coda/server-app test:unit           # server only
pnpm --filter @coda/client-app test:unit           # client only
```

**Functional tests** - need infra running:

```bash
MYSQL_HOST_PORT=0 docker compose --project-name coda-test-functional --profile test-functional up -d mysql redis migrate
pnpm --filter @coda/server-app test:functional
```

**Integration tests** - need backend stack:

```bash
MYSQL_HOST_PORT=0 docker compose --project-name coda-test-integration --profile test-integration up -d mysql redis migrate server
pnpm --filter @coda/server-app test:integration
```

**E2E tests** - need full production stack:

```bash
docker compose --project-name coda-test-e2e down --remove-orphans 2>/dev/null
MYSQL_HOST_PORT=0 docker compose --project-name coda-test-e2e --profile preview up --build -d
pnpm docker:test:e2e
```

### Override test command in Docker

```bash
docker compose --project-name coda-test-unit --profile test-unit \
  run lint-and-test pnpm --filter @coda/server-app test:unit
```

## Services & Ports

| Service          | Container port | Host port                 | Profile                                  |
| ---------------- | -------------- | ------------------------- | ---------------------------------------- |
| mysql            | 3306           | 6789 (dev), random (test) | dev, preview, test-\*                    |
| redis            | 6379           | — (internal only)         | dev, preview, test-\*                    |
| s3               | 9000 / 9001    | 9000 / 9001               | dev, preview, test-integration, test-e2e |
| migrate          | -              | -                         | dev, preview, test-\* (one-shot)         |
| server           | 8080           | 8080                      | dev, test-integration                    |
| client-dev       | 5173           | 6005                      | dev                                      |
| production       | 8080           | 8080                      | production                               |
| preview          | 8080           | 6005                      | preview, test-e2e                        |
| lint-and-test    | -              | -                         | test-unit                                |
| test-functional  | -              | -                         | test-functional                          |
| test-integration | -              | -                         | test-integration                         |
| test-e2e         | -              | -                         | test-e2e                                 |

## Database

- **MySQL 8.0.36** with RAM-backed storage (`tmpfs`) - data is ephemeral
- **Root user** (`root`/`root`) - used by migration runner for DDL
- **App user** (`coda_app`/`coda_app_pass`) - used by server for DML
- Migrations run automatically via one-shot `migrate` container on every `docker compose up`
- To connect directly: `mysql -h 127.0.0.1 -P 6789 -u coda_app -pcoda_app_pass coda`

## Troubleshooting

**Port already in use:**

```bash
pnpm docker:down        # stop dev containers
pnpm docker:test:down   # stop all test containers
lsof -i :8080           # check what's using the port
```

**Stale containers/images:**

```bash
pnpm docker:clean       # dev project
pnpm docker:test:clean  # all test projects
```

**Migration fails:**
Check that mysql is healthy first: `docker compose --project-name coda ps` (dev) or `docker compose --project-name coda-test-functional ps` (test)

**HMR not working in Docker:**
Ensure `DOCKER=true` is set in the client-dev container environment (already configured in docker-compose.yml). Vite falls back to polling mode for file watching.
