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

# Deployment

> How MUZE is deployed to Render, Firebase Hosting, and Neon across staging and production

## Environment map

| Environment      | Frontend                                  | Backend                             | Database                 |
| ---------------- | ----------------------------------------- | ----------------------------------- | ------------------------ |
| **Production**   | `app.muzemus.online` (Firebase Hosting)   | `api.muzemus.online` (Render)       | Neon production database |
| **Staging**      | `stage.muzemus.online` (Firebase Hosting) | `stage-api.muzemus.online` (Render) | Neon staging database    |
| **Landing**      | `muzemus.online` (Firebase Hosting)       | —                                   | —                        |
| **Docs**         | `doc.muzemus.online` (Mintlify)           | —                                   | —                        |
| **Architecture** | `system.muzemus.online` (Mintlify)        | —                                   | —                        |

> **Note on API domains:** Render's free plan does not support custom domains. The `api.muzemus.online` and `stage-api.muzemus.online` records only resolve once the backend services are on a paid Render plan (Starter or higher) with the custom domain attached. Until then, the frontend can point at the `.onrender.com` URLs instead (see [Domains](/operations/domains)).

## Deployment architecture

```mermaid theme={null}
flowchart LR
    subgraph Git["Git repository"]
        Main["main branch"]
        Stage["staging branch"]
        PR["Pull request"]
    end

    subgraph CI["GitHub Actions"]
        Gate["Quality gate<br/>typecheck · lint · build · test"]
        MigrateS["Migrate staging DB"]
        MigrateP["Migrate prod DB"]
    end

    subgraph Render["Render"]
        BackendS["NestJS API (staging)<br/>muze-uniform-backend-stage"]
        BackendP["NestJS API (prod)<br/>muze-uniform-backend"]
    end

    subgraph Firebase["Firebase Hosting"]
        FrontendS["React SPA (staging)<br/>muze-mus-stage"]
        FrontendP["React SPA (prod)<br/>muze-mus"]
        Landing["Landing page<br/>muze-mus-landing"]
    end

    subgraph Neon["Neon"]
        DBS["PostgreSQL (staging)"]
        DBP["PostgreSQL (prod)"]
    end

    PR --> Gate
    Stage --> Gate --> MigrateS --> BackendS --> FrontendS
    Main --> Gate --> MigrateP --> BackendP --> FrontendP
    Main --> Landing
    FrontendS -->|HTTPS| BackendS
    FrontendP -->|HTTPS| BackendP
    BackendS -->|Pooled connection| DBS
    BackendP -->|Pooled connection| DBP
```

## Development workflow

```mermaid theme={null}
flowchart LR
    A["Create branch"] --> B["Implement"] --> C["Test locally"] --> D["Push branch"]
    D --> E["CI quality gate"] --> F["Deploy to stage"] --> G["Verify stage"]
    G --> H["Merge to main"] --> I["Production"]
```

1. **Create branch** — feature branches off `main` (e.g. `feat/...`, `fix/...`).
2. **Implement** — code changes in `backend/` and/or `frontend/`.
3. **Test locally** — `pnpm dev`, `pnpm --filter backend test`, `pnpm --filter frontend test`.
4. **Push branch** — the `ci.yml` workflow runs the quality gate on every push/PR.
5. **Deploy to stage** — merge the branch into `staging` (or push directly); `deploy.yml` migrates the staging database, deploys the staging backend to Render, then the staging frontend to Firebase.
6. **Verify stage** — check `stage.muzemus.online` and the staging health endpoint.
7. **Merge to main** — open a PR from `staging` (or the feature branch) into `main`.
8. **Production** — `deploy.yml` migrates the production database, deploys the production backend, frontend, and landing page.

## CI/CD pipelines

### `ci.yml` — pull request verification

Runs on every pull request and every push to a non-`main`/`staging` branch:

* Format check, typecheck, lint, build, and unit tests for `backend` and `frontend`.

### `deploy.yml` — staging and production

Triggered on push to `staging` or `main`. Jobs are gated by the shared quality gate and use GitHub Environments (`staging` / `production`) so the same secret names hold environment-specific values.

| Job                 | Staging (`staging` branch)                                      | Production (`main` branch)                     |
| ------------------- | --------------------------------------------------------------- | ---------------------------------------------- |
| `migrate-*`         | `prisma migrate deploy` against staging DB                      | `prisma migrate deploy` against prod DB        |
| `deploy-backend-*`  | Render deploy hook for `muze-uniform-backend-stage`             | Render deploy hook for `muze-uniform-backend`  |
| `deploy-frontend-*` | Firebase `hosting:muze-mus-stage` (built with `--mode staging`) | Firebase `hosting:muze-mus` (production build) |
| `deploy-landing`    | —                                                               | Firebase `hosting:muze-mus-landing`            |

After each backend deploy, the pipeline polls `GET /api/v1/health` until the service responds.

## Backend deployment (Render)

### Service configuration

| Setting      | Value                                                                       |
| ------------ | --------------------------------------------------------------------------- |
| Service type | Web service                                                                 |
| Runtime      | Node.js 20                                                                  |
| Plan         | Free (custom domains require Starter+)                                      |
| Auto-deploy  | **Off** (`autoDeploy: false` in `render.yaml`); deploys are triggered by CI |

Two services are defined in `backend/render.yaml`:

* `muze-uniform-backend` — production
* `muze-uniform-backend-stage` — staging

### Build and start commands

```bash theme={null}
# build
pnpm install && pnpm prisma:generate && pnpm build

# start
node dist/src/main
```

Migrations are applied by the CI pipeline *before* the new backend version is activated, so the database schema is always current when a new version deploys.

### Database connections

The backend uses two connection strings against Neon:

| Variable       | Used for                | Connection                                                      |
| -------------- | ----------------------- | --------------------------------------------------------------- |
| `DATABASE_URL` | Application queries     | Pooled (PgBouncer, transaction mode)                            |
| `DIRECT_URL`   | `prisma migrate deploy` | Direct, because DDL cannot run through transaction-mode pooling |

Staging uses its own Neon database with its own `DATABASE_URL` / `DIRECT_URL` (stored in the `staging` GitHub Environment and the staging Render service).

## Frontend deployment (Firebase Hosting)

```bash theme={null}
cd frontend
pnpm install
pnpm build              # production build (reads .env.production)
pnpm build:staging      # staging build (reads .env.staging)
firebase deploy --only hosting:muze-mus        # production site
firebase deploy --only hosting:muze-mus-stage  # staging site
```

### Firebase sites

| Site               | Purpose             | Domain                 |
| ------------------ | ------------------- | ---------------------- |
| `muze-mus`         | Production portal   | `app.muzemus.online`   |
| `muze-mus-stage`   | Staging portal      | `stage.muzemus.online` |
| `muze-mus-landing` | Public landing page | `muzemus.online`       |

The frontend `firebase.json` declares `"site": "muze-mus"`; CI passes the explicit site name to `firebase deploy --only hosting:<site>`.

## Landing page

The landing page is a static site in `landing/` (plain HTML/CSS, no build step). It is deployed to the `muze-mus-landing` Firebase site on every push to `main`.

## Deployment checklist

Before deploying:

* [ ] All tests pass locally
* [ ] No TypeScript errors
* [ ] Environment variables are set in the Render dashboard and GitHub Environments
* [ ] Database migrations are compatible with running code
* [ ] No secrets in committed code

After deploying:

* [ ] Backend health check returns 200 (`GET /api/v1/health`)
* [ ] Frontend loads and login works
* [ ] API calls from the frontend succeed with no CORS errors
* [ ] Staging verified before merging to `main`

## Rollback

| Layer    | Rollback method                                                                          |
| -------- | ---------------------------------------------------------------------------------------- |
| Backend  | Redeploy the previous version from the Render dashboard, or `git revert` + manual deploy |
| Frontend | `firebase deploy` with a previous build, or Firebase console rollback                    |
| Database | Neon point-in-time restore (see [Backups and recovery](/operations/backups-recovery))    |
