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

# Deployments

> CI, staging, and production deployment shape.

ThinkEx deploys from Cloudflare Workers Builds. GitHub Actions only validates pull requests. Deploy, migration, and secret-management commands are operational actions and should not be run casually from a local checkout.

## Branches and Environments

| Environment | URL | Worker | Trigger |
| - | - | - | - |
| Production | `https://thinkex.app` | `thinkex` | Push to `main` |
| Staging | `https://staging.thinkex.app` | `thinkex-staging` | Push to `staging` |

## CI

The `CI` workflow runs on pull requests only. It installs with Vite+ and runs check, test, and a staging-shaped build:

```bash theme={null}
vp run ciCheck && vp run ciTest && vp run ciBuildStaging
```

`ciBuildStaging` compiles with `CLOUDFLARE_ENV=staging` so PR CI exercises real Wrangler env flattening. PostHog build secrets are optional for that path (analytics stays off when unset); production deploy still requires the full production PostHog set, including the feedback survey id. Local `vp run verify` stays on the generic `ciBuild` (`build:app`) so contributors without Infisical are unaffected. When changes go through a pull request, the `validate` check must be green before merge.

The separate `React Doctor` workflow runs on pull requests and pushes to `main`. It scans changed React files on pull requests, posts inline diagnostics, and records the React Doctor score without folding those comments into the main Vite+ verification job.

## Deploy

Two Workers Builds configurations watch this repository, one per branch. Each runs a build command, then a deploy command that applies Postgres migrations before shipping the Worker:

| Branch | Build | Deploy |
| - | - | - |
| `main` | `pnpm run build:production` | `pnpm run db:migrate && pnpm run deploy:worker` |
| `staging` | `pnpm run build:staging` | `pnpm run db:migrate:staging && pnpm run deploy:worker:staging` |

`CLOUDFLARE_ENV` is set per configuration in the Cloudflare dashboard, which is why `deploy:worker` takes no `--env` flag. Running it from a local checkout without that variable targets the `thinkex-dev` Worker, not production.

Build variables and secrets (Cloudflare API token, PostHog production set) live in the Workers Builds settings. Each deployment configuration also needs the environment's direct PlanetScale administrative connection string as the build-time `DATABASE_URL`; it is used only by Drizzle Kit and is not a Worker runtime secret. The Worker itself connects with the environment's cache-disabled `HYPERDRIVE` binding and a restricted runtime database role.

## Container Images

Production and staging pin container images to immutable `registry.cloudflare.com` digests (`wrangler.jsonc`), so ordinary deploys never need Docker and Workers Builds can run without a Docker daemon. The top-level config still points at the Dockerfiles for `wrangler dev`.

The Dockerfiles change rarely, so there is no workflow for this. When one does change, rebuild and repin by hand from a machine with Docker:

```bash theme={null}
wrangler containers build ./containers/gotenberg \
  --tag thinkex-officepdfconverter-production:<new-tag> --push

wrangler containers images list --filter thinkex-officepdfconverter
```

Copy the digest from the second command into the `containers` blocks for both `staging` and `production` in `wrangler.jsonc`, then push. Both environments intentionally share one image per container.

<Warning>
  Remote migrations and deploy commands affect shared environments. Run them only when you are intentionally doing release work.
</Warning>

## Secrets

Runtime and deploy secrets originate in Infisical. Build-time secrets (Cloudflare credentials, PostHog variables) are set in each Workers Builds configuration; runtime secrets declared by the Worker configuration are set on the Worker itself. GitHub only holds what PR CI needs.

Direct workspace uploads additionally require bucket-scoped R2 Object Read & Write credentials in `R2_ACCESS_KEY_ID` and `R2_SECRET_ACCESS_KEY`, `R2_ACCOUNT_ID`, and an independent random `WORKSPACE_UPLOAD_TOKEN_SECRET` for completion claims. Configure browser PUT access from the checked-in policies:

```bash theme={null}
wrangler r2 bucket cors set thinkex-workspace-kernel-files-staging --file config/r2/cors.staging.json
wrangler r2 bucket cors set thinkex-workspace-kernel-files --file config/r2/cors.production.json
```

After inspecting existing lifecycle rules, add a one-day expiry for abandoned direct-upload objects in each bucket:

```bash theme={null}
wrangler r2 bucket lifecycle add thinkex-workspace-kernel-files-staging expire-abandoned-workspace-file-uploads workspace_file_uploads/ --expire-days 1
wrangler r2 bucket lifecycle add thinkex-workspace-kernel-files expire-abandoned-workspace-file-uploads workspace_file_uploads/ --expire-days 1
```

<Warning>
  CORS `set` replaces the bucket's current CORS policy. Reconcile the checked-in policy with any existing rules before applying it.
</Warning>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.