Skip to content

Commit 9cd5962

Browse files
Add Render deploy step for server and nlp-service images
Picks Render "deploy an existing image" as the hosting target for both GHCR-published images (server on :3000, nlp-service on :8000) since it natively supports pulling from a private registry and exposes a deploy-hook API that CI can trigger with a specific image tag. Fly.io and AWS were ruled out as heavier fits for this project's current stage (see docs/deployment.md for the full comparison). The docker-publish workflow gains deploy-server/deploy-nlp-service jobs that POST the commit-SHA-tagged image to each service's Render deploy hook. Both jobs no-op until RENDER_DEPLOY_HOOK_SERVER / RENDER_DEPLOY_HOOK_NLP_SERVICE secrets exist, so this is safe to merge ahead of the one-time Render setup, which is documented in docs/deployment.md along with the required env vars and secrets.
1 parent a43d124 commit 9cd5962

2 files changed

Lines changed: 123 additions & 0 deletions

File tree

.github/workflows/docker-publish.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,37 @@ jobs:
6363
tags: |
6464
${{ steps.image.outputs.name }}:latest
6565
${{ steps.image.outputs.name }}:${{ github.sha }}
66+
67+
# Deploy jobs trigger a Render deploy hook pinned to the image tag that was
68+
# just published (see docs/deployment.md for the one-time Render setup).
69+
# They no-op until the corresponding secret is configured, so this workflow
70+
# is safe to merge ahead of that setup.
71+
deploy-server:
72+
name: Deploy server to Render
73+
needs: build-server
74+
runs-on: ubuntu-latest
75+
if: github.ref == 'refs/heads/main' && secrets.RENDER_DEPLOY_HOOK_SERVER != ''
76+
steps:
77+
- name: Trigger Render deploy hook
78+
env:
79+
REGISTRY: ${{ env.REGISTRY }}
80+
DEPLOY_HOOK_URL: ${{ secrets.RENDER_DEPLOY_HOOK_SERVER }}
81+
IMAGE_TAG: ${{ github.sha }}
82+
run: |
83+
curl -fsS -X POST \
84+
"${DEPLOY_HOOK_URL}&imgURL=${REGISTRY}/${GITHUB_REPOSITORY,,}/server:${IMAGE_TAG}"
85+
86+
deploy-nlp-service:
87+
name: Deploy nlp-service to Render
88+
needs: build-nlp-service
89+
runs-on: ubuntu-latest
90+
if: github.ref == 'refs/heads/main' && secrets.RENDER_DEPLOY_HOOK_NLP_SERVICE != ''
91+
steps:
92+
- name: Trigger Render deploy hook
93+
env:
94+
REGISTRY: ${{ env.REGISTRY }}
95+
DEPLOY_HOOK_URL: ${{ secrets.RENDER_DEPLOY_HOOK_NLP_SERVICE }}
96+
IMAGE_TAG: ${{ github.sha }}
97+
run: |
98+
curl -fsS -X POST \
99+
"${DEPLOY_HOOK_URL}&imgURL=${REGISTRY}/${GITHUB_REPOSITORY,,}/nlp-service:${IMAGE_TAG}"

docs/deployment.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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

Comments
 (0)