|
| 1 | +# Deployment |
| 2 | + |
| 3 | +## Target: Render, "Deploy an existing image" (Web Services) |
| 4 | + |
| 5 | +`server` (Express, port 3000) and `nlp-service` (FastAPI, port 8000) are both |
| 6 | +stateless HTTP services with no local database — `server.ts` talks to an |
| 7 | +external data platform (`MUJARRAD_*` env vars) and both services call the |
| 8 | +Gemini API (`GEMINI_API_KEY`). Neither has a persistence requirement that |
| 9 | +would push this toward a heavier platform. |
| 10 | + |
| 11 | +Options considered, and why they were ruled out: |
| 12 | + |
| 13 | +- **Vercel** — built for serverless/edge functions and static frontends, not |
| 14 | + a great fit for two always-on containers with their own process/port. |
| 15 | +- **Fly.io** — good for plain Docker apps, but it has no native way to pull |
| 16 | + from a third-party private registry like GHCR. The supported workaround is |
| 17 | + to `docker pull` the GHCR image in CI and re-push it into Fly's own |
| 18 | + registry before `flyctl deploy` — an extra hop that duplicates image |
| 19 | + storage for no benefit here. |
| 20 | +- **AWS (ECS/Fargate)** — can pull directly from a private registry, but |
| 21 | + needs a VPC, cluster, task definitions, and an ALB provisioned first. That |
| 22 | + is more standing infrastructure than this repo's current stage justifies. |
| 23 | +- **Render** — supports "Deploy an existing image" directly from a private |
| 24 | + GHCR image via a stored registry credential, and gives each service a |
| 25 | + deploy-hook URL that CI can call with a specific image tag. This is the |
| 26 | + smallest amount of new infrastructure that satisfies "pull the published |
| 27 | + GHCR image and run it." |
| 28 | + |
| 29 | +Both services deploy to Render as separate Web Services running the |
| 30 | +prebuilt images published by `.github/workflows/docker-publish.yml` |
| 31 | +(`ghcr.io/wider-community/resolve-ai/server` and `.../nlp-service`). |
| 32 | + |
| 33 | +## One-time setup (needs a human with Render account access) |
| 34 | + |
| 35 | +This repo's CI cannot create Render resources or hold Render/GHCR |
| 36 | +credentials, so the following is a manual setup, done once, by whoever owns |
| 37 | +(or is granted) the Wider-Community Render account: |
| 38 | + |
| 39 | +1. **Registry credential** — in the Render workspace, go to |
| 40 | + Settings → Container Registry Credentials → add a GHCR credential using a |
| 41 | + GitHub Personal Access Token with `read:packages` scope (a token from a |
| 42 | + dedicated bot/service account is preferable to a personal one — the |
| 43 | + built-in per-workflow `GITHUB_TOKEN` can't be used here since it's |
| 44 | + ephemeral and can't be typed into the Render dashboard). |
| 45 | + |
| 46 | +2. **`resolve-ai-server` Web Service** — "Deploy an existing image": |
| 47 | + - Image: `ghcr.io/wider-community/resolve-ai/server:latest`, using the |
| 48 | + credential from step 1. |
| 49 | + - Port: `3000` |
| 50 | + - Env vars: `GEMINI_API_KEY`, `MUJARRAD_API_PUBLIC_KEY`, |
| 51 | + `MUJARRAD_API_SECRET_KEY`, `MUJARRAD_SPACE_SLUG` — values come from |
| 52 | + whoever owns those credentials; they don't live in this repo or CI. |
| 53 | + |
| 54 | +3. **`resolve-ai-nlp-service` Web Service** — "Deploy an existing image": |
| 55 | + - Image: `ghcr.io/wider-community/resolve-ai/nlp-service:latest`, same |
| 56 | + credential. |
| 57 | + - Port: `8000` |
| 58 | + - Env vars: `GEMINI_API_KEY` |
| 59 | + |
| 60 | +4. **Deploy hooks** — on each service, Settings → Deploy Hook, copy the URL, |
| 61 | + and add it as a GitHub Actions repository secret: |
| 62 | + - `RENDER_DEPLOY_HOOK_SERVER` |
| 63 | + - `RENDER_DEPLOY_HOOK_NLP_SERVICE` |
| 64 | + |
| 65 | +Until these secrets exist, the deploy jobs added in this PR skip themselves |
| 66 | +(see below) — merging this PR does not require the Render services to exist |
| 67 | +yet, but nothing will actually deploy until someone completes the steps |
| 68 | +above. |
| 69 | + |
| 70 | +## CI/CD wiring |
| 71 | + |
| 72 | +`.github/workflows/docker-publish.yml` gets a `deploy-server` and a |
| 73 | +`deploy-nlp-service` job, each running after its image finishes publishing |
| 74 | +on a push to `main`. Each job POSTs to its Render deploy hook with an |
| 75 | +`imgURL` query param pinned to the commit SHA that was just built, so the |
| 76 | +service that comes up always matches the exact commit that triggered the |
| 77 | +build rather than a possibly-stale `:latest`. |
| 78 | + |
| 79 | +Each deploy job is skipped (not failed) when its secret isn't set yet, so |
| 80 | +this workflow change is safe to merge ahead of the Render setup above. |
| 81 | + |
| 82 | +## Follow-ups intentionally out of scope here |
| 83 | + |
| 84 | +- Render's free tier spins services down after inactivity (slow cold |
| 85 | + start on the next request). Fine for now; revisit if this becomes |
| 86 | + user-facing with latency requirements. |
| 87 | +- No staging environment — both services deploy straight to what Render |
| 88 | + calls production on every push to `main`, matching the rest of this |
| 89 | + repo's CI/CD (no branch protection yet either — see WID-216). |
0 commit comments