Render free-tier backend deployment
This document describes the minimal backend-only deployment path for Render free-tier.
What this covers
- Django backend only
- PostgreSQL provided by Render or an external managed database
- Gunicorn web service
- Static files collected at boot
Required environment variables
Set these in the Render dashboard:
SECRET_KEYDATABASE_URLALLOWED_HOSTSCSRF_TRUSTED_ORIGINSOPENAI_API_KEY(if needed)- any
GITHUB_APP_*values if the OAuth integration is enabled GOOGLE_AUTHORIZED_REDIRECT_URIS/GITHUB_AUTHORIZED_REDIRECT_URIS(comma-separated, since 0.1.90) — set when more than one frontend app shares this backend so each app's sign-in lands back on its own host. Falls back to the single-valueGOOGLE_AUTHORIZED_REDIRECT_URI/GITHUB_AUTHORIZED_REDIRECT_URIwhen unset.- any
GITHUB_DATA_SYNC_APP_*values for the experimental per-user GitHub data-sync (since 0.1.90); the push pipeline is gated untilpyjwt + cryptographyship inbackend/requirements*.txt
Render also provides:
PORT
Recommended extras:
PYTHONUNBUFFERED=1WEB_CONCURRENCY=2
Build command
Use one of these:
pip install -r backend/requirements.txt
or if the service root is backend/:
pip install -r requirements.txt
Start command
Preferred:
bash deployment/render/scripts/render_backend_start.sh
If the service root is backend/, use:
bash ../deployment/render/scripts/render_backend_start.sh
What the start script does
deployment/render/scripts/render_backend_start.sh runs:
python manage.py migrate --noinputpython manage.py bootstrap_platform || truepython manage.py collectstatic --noinput --cleargunicorn notechondria.wsgi:application --bind 0.0.0.0:$PORT
Cloudflare R2 storage (required)
Render free-tier has an ephemeral filesystem — user-uploaded media files are lost on every restart. Cloudflare R2 provides S3-compatible persistent storage.
Setup
- Create an R2 bucket in the Cloudflare dashboard.
- Create an API token under R2 > Manage R2 API Tokens with read/write access to the bucket.
- (Optional) Connect a custom domain or enable the
r2.devsubdomain for public access to the bucket. - Set these environment variables in the Render dashboard:
| Variable | Description |
|---|---|
CLOUDFLARE_R2_BUCKET_NAME | R2 bucket name |
CLOUDFLARE_R2_ACCOUNT_ID | Cloudflare account ID (found in the dashboard URL or API section) |
CLOUDFLARE_R2_ACCESS_KEY_ID | R2 API token access key |
CLOUDFLARE_R2_SECRET_ACCESS_KEY | R2 API token secret key |
CLOUDFLARE_R2_CUSTOM_DOMAIN | (optional) Public hostname for the bucket (e.g. cdn.example.com) |
When CLOUDFLARE_R2_BUCKET_NAME is set, Django automatically uses R2 for both
static files (collectstatic) and media files (user uploads, avatars, course
images). If the bucket name is set but any of the three required credentials
are missing, the app will fail to start with a clear error message.
How it works
collectstatic --noinput --clearuploads built static assets to<bucket>/static/on every deploy.- User-uploaded media is stored under
<bucket>/media/. - URLs are generated pointing to the custom domain (if set) or the R2 S3 endpoint.
Docker Compose (local)
When deploying with Docker Compose, do not set CLOUDFLARE_R2_BUCKET_NAME.
The backend will use the local filesystem with persistent Docker volumes, and
nginx will serve static and media files directly.
Notes
- This is backend-only. The three frontend apps deploy separately to GitHub Pages.
- Free-tier instances may cold-start slowly.
- If migrations are slow, startup time may increase.
- If
bootstrap_platformis not needed for a given environment, it safely tolerates failure in the script.