> For the complete documentation index, see [llms.txt](https://cuesoft.gitbook.io/expendit/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://cuesoft.gitbook.io/expendit/system/deployment.md).

# Deployment

> Implements decision X-3. Cloud deploys follow the proven cueprise/getpp patterns (workflows verified 2026-07-16); provisioning goes through the **cuesoft-iac** Pulumi ecosystem — never ad-hoc gcloud. Self-hosting via `deploy/` (compose/helm/terraform) is unchanged and shares only images.

## 1. Topology

| Surface           | Runs on                  | Provisioned by               |
| ----------------- | ------------------------ | ---------------------------- |
| `api/common` (Go) | Cloud Run                | cuesoft-iac stack `expendit` |
| `web` (Next.js)   | **Firebase App Hosting** | App Hosting backend          |

## 2. Provisioning (cuesoft-iac)

* **Pulumi, bun runtime**; state in `gs://cuesoft-iac-pulumi-state-bucket`; per-product stack (`Pulumi.<product>.yaml`) added alongside existing ones (getpp, swaves, …).
* Cloud Run services instantiate the shared module `common/modules/gcp/cloud-run-service.ts`; the GitHub→GCP deploy identity uses **Workload Identity Federation** (`common/helpers/wif.ts`) — no service-account keys in GitHub.
* Secrets/env flow from **Doppler** (`cueprise/cuesoft_stg` pattern) into Cloud Run env; the repo's `.env.example` files document the variable names, Doppler owns values.

## 3. CI/CD (GitHub Actions, cueprise/getpp pattern)

| Workflow             | Trigger                    | Does                                                                                                                                                                                                        |
| -------------------- | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `build-and-test.yml` | PRs **and** push to `main` | build + tests per service — **no deploy, no image push** (X-6: open-source repos; merges must be inert)                                                                                                     |
| `release.yml`        | **tag `v*` created**       | matrix over services: buildx (GHA cache) → push `cuesoft/expendit-<service>` (tags: `latest`, `sha`, version) → Cloud Run deploy **by image digest** via WIF → App Hosting rollout pinned to the tag commit |

**Gating (X-6):** `stg` (sandbox) is the only environment and is treated as production. Two independent gates: (1) a GitHub **tag ruleset** restricts creating `v*` tags to owner-level access; (2) the deploy job runs in a protected GitHub **environment** (`Sandbox`) with required reviewers. A merged PR never reaches the sandbox on its own.

Two hard-won rules inherited from cueprise, non-negotiable:

1. **Deploy by digest, never by tag** — Cloud Run pulls Docker Hub images through the `mirror.gcr.io` cache, which has served stale manifests for tags; the staging workflow threads `steps.build.outputs.digest` into the deploy step.
2. **WIF only** (`google-github-actions/auth@v3` with `workload_identity_provider` + `service_account`) — no JSON keys.

The single protected GitHub environment is **`Sandbox`** (X-6) — required reviewers + the sandbox URLs (`api.expendit.cuesoft.io`). No other deploy environments exist.

## 4. Runtime contract (Cloud Run) **\[Decided defaults]**

| Service                    | CPU / mem        | Concurrency             | Min–max instances | Timeout |
| -------------------------- | ---------------- | ----------------------- | ----------------- | ------- |
| api/common                 | 1 vCPU / 512 MiB | 80                      | 0–5               | 60 s    |
| import worker (same image) | 1 vCPU / 1 GiB   | 1 (AI-budget isolation) | 0–3               | 300 s   |

* Domain: `api.expendit.cuesoft.io` → api/common.
* Rollback: redeploy the previous image digest (recorded in the release run).
* Web env: `NEXT_PUBLIC_*` flows Doppler → `apphosting.yaml` at rollout.

## 5. Not in this phase

Writing these workflows + the Pulumi stack is **implementation work**, out of scope for the docs phase — this document is the contract they'll be built against. Docker Hub repos already exist for every image name above.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://cuesoft.gitbook.io/expendit/system/deployment.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
