Semestra

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 .env

Pick a setup

Where it runsAddress users openGuide
Your computer, or a machine on your home networkhttp://localhost:8000 or http://<ip>:8000Computer or local network
A server with a domain, HTTPS by the bundled Caddyhttps://semestra.example.comServer with a domain
A server that already runs nginx, Traefik or another proxyhttps://semestra.example.comBehind 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.env safe 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

SymptomCheck
The container keeps restartingdocker compose logs semestra
After signing in you are back on the sign-in pageSEMESTRA_PUBLIC_URL starts with https:// but you opened an http:// address. Open the https one
No verification code arrivesWithout 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 mountsudo 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.