Self-hosting
Run your own instance with Docker Compose. Pick the setup that matches where it runs.
One container serves the API and the web app on port 8000, and all data lives in one Docker volume. You need Docker with the Compose plugin and about 4 GB of RAM (1 GB with the smaller image).
The repository and the images are public, so there is nothing to sign in to. Every setup starts the same way:
git clone https://github.com/jinyuanZhou-Leo/Semestra-Next.git
cd Semestra-Next/deploy/selfhost
cp .env.example .envPick a setup
| Where it runs | Address users open | Guide |
|---|---|---|
| Your computer, or a machine on your home network | http://localhost:8000 or http://<ip>:8000 | Computer or local network |
| A server with a domain, HTTPS by the bundled Caddy | https://semestra.example.com | Server with a domain |
| A server that already runs nginx, Traefik or another proxy | https://semestra.example.com | Behind your own reverse proxy |
Then, for any setup:
- Email: send sign-in codes by email instead of printing them to the log.
- Upgrade and back up: keep
/data/secrets.envsafe before anything else. - Images: the default image or the smaller one, a fixed version, or your own build.
- Optional features: Canvas school sign-in, a shared AI provider.
- All settings
Troubleshooting
| Symptom | Check |
|---|---|
| The container keeps restarting | docker compose logs semestra |
| After signing in you are back on the sign-in page | SEMESTRA_PUBLIC_URL starts with https:// but you opened an http:// address. Open the https one |
| No verification code arrives | Without email it is in docker compose logs semestra |
| "SEMESTRA_SECRET_KEY and SEMESTRA_ENCRYPTION_KEY must be set in prod" | SEMESTRA_AUTO_SECRETS was removed from the compose file and no keys are set |
| "SEMESTRA_ENCRYPTION_KEY is not a Fernet key" | The SEMESTRA_ENCRYPTION_KEY in your .env is not a key made by Fernet.generate_key() (44 characters ending in =). Remove it to use the generated one, or restore the key you backed up |
| Permission errors on a bind mount | sudo chown -R 10001:10001 <dir>: the container runs as uid 10001 |
/healthz answers while the process is up, /readyz once the database and the background worker
are ready (otherwise 503). The image has a Docker HEALTHCHECK built in.