Render
On Render, the server is one web service built from your Dockerfile, and the store is an R2 bucket. The service is described in a render.yaml Blueprint.
The service has a small disk it never uses. Render normally starts the new instance and stops the old one a minute after traffic moves, which is the opposite of what Akter needs. With a disk attached, Render stops the old instance before it starts the new one. Disks need a paid instance type, and the free one would sleep anyway.
You need the src/server.ts, Dockerfile and .dockerignore from Deploying, an R2 bucket, and the project in a Git repository Render can read.
Describe the service
Create render.yaml in the repository root:
services:
- type: web
name: my-app
runtime: docker
plan: starter
numInstances: 1
healthCheckPath: /ready
disk:
name: handoff
mountPath: /handoff
sizeGB: 1
envVars:
- key: AKTER_STORAGE_BUCKET
value: my-app-store
- key: AKTER_STORAGE_ENDPOINT
value: https://ACCOUNT_ID.r2.cloudflarestorage.com
- key: AKTER_STORAGE_REGION
value: auto
- key: AWS_ACCESS_KEY_ID
sync: false
- key: AWS_SECRET_ACCESS_KEY
sync: false
- key: AUTH_ISSUER
value: https://login.example.com/
- key: AUTH_AUDIENCE
value: my-app
- key: AUTH_JWKS_URL
value: https://login.example.com/.well-known/jwks.jsondiskattaches the disk, for the reason above.numInstances: 1runs one server.healthCheckPathmakes Render wait for/readybefore it sends traffic, and stop sending it while/readyfails.sync: falsemarks the bucket's keys as secrets. Render asks for them when you create the Blueprint and never reads them from the file.
Render sets PORT, and the server listens on it. With a disk, Render gives the server 30 seconds between SIGTERM and SIGKILL.
Deploy
Push render.yaml, then in the Render dashboard choose New, then Blueprint, and pick the repository. Enter the two keys when Render asks, and deploy. Check the server:
curl https://<service url>/readyReleases
Each push to the branch deploys. Render stops the old instance, waits for it to drain, and starts the new one. Expect a short gap; clients retry through it.