Deployment¶
EndoCore is a standard ASGI app served by uvicorn. Anything that runs ASGI
runs EndoCore.
The ASGI entry point¶
# provided by the package
endocore.asgi:create_app # a factory that builds an Application from the CWD
create_app() reads the current working directory as the app root and honours
three env vars:
ENDOCORE_DEV=1— turn dev mode on (the in-process file watcher,/docs+/openapi.json, and a relaxed websocket same-origin check for a local frontend on another port).endo devsets this for you; a bareuvicorn endocore.asgi:create_app --factorywith no env vars at all is off by default — dev mode is opt-in, not opt-out, so forgetting to set anything fails toward the safer production posture.ENDOCORE_DEFAULT_VERSION=latest— resolve version-less paths.ENDOCORE_OPENAPI=1— serve/docs+/openapi.jsoneven with dev mode off. Off by default in production on purpose — opt in explicitly if you want the schema/UI publicly reachable.
Uvicorn (single process)¶
Uvicorn workers via Gunicorn (multi-process)¶
pip install gunicorn
gunicorn "endocore.asgi:create_app()" \
--worker-class uvicorn.workers.UvicornWorker \
--workers 4 --bind 0.0.0.0:8000
Rule of thumb: workers = 2 × CPU cores + 1. Because the ORM offloads to a
threadpool, workers keep the event loop free under DB load.
Docker¶
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
ENV ENDOCORE_DEV=0
EXPOSE 8000
CMD ["uvicorn", "endocore.asgi:create_app", "--factory", "--host", "0.0.0.0", "--port", "8000"]
requirements.txt:
For production, prefer Gunicorn + Uvicorn workers:
CMD ["gunicorn", "endocore.asgi:create_app()", \
"--worker-class", "uvicorn.workers.UvicornWorker", \
"--workers", "4", "--bind", "0.0.0.0:8000"]
Nginx reverse proxy¶
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Request-ID $request_id;
}
location /ws/ { # websockets
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
Then trust the proxy headers in your app:
from endocore.middleware import proxy_headers_middleware
middlewares = [proxy_headers_middleware(trusted=["127.0.0.1"])]
systemd service¶
# /etc/systemd/system/endocore.service
[Unit]
Description=EndoCore app
After=network.target
[Service]
WorkingDirectory=/srv/app
Environment=ENDOCORE_DEV=0
ExecStart=/srv/app/.venv/bin/gunicorn endocore.asgi:create_app() \
--worker-class uvicorn.workers.UvicornWorker --workers 4 --bind 0.0.0.0:8000
Restart=always
[Install]
WantedBy=multi-user.target
Migrations on deploy¶
Run migrations before switching traffic:
PaaS (Render / Railway / Fly.io / Heroku-style)¶
- Build:
pip install -r requirements.txt - Start:
gunicorn endocore.asgi:create_app() --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:$PORT - Env: your
DATABASE_URL, secrets for cookies/CSRF andENDOCORE_FILE_KEY(dev mode is off by default — noENDOCORE_DEVneeded unless you want it on).
Production checklist¶
- [ ] Don't set
ENDOCORE_DEV=1(it's off by default; no watcher,/docsoff). - [ ] Leave
ENDOCORE_OPENAPIunset unless you deliberately want the schema public — don't set it to "1" out of habit. - [ ] Multiple workers behind a proxy.
- [ ]
endo migrateon each release. - [ ] Secrets from env (cookie/CSRF secret,
ENDOCORE_FILE_KEY, DB creds). - [ ] Security middleware enabled (see Security).
- [ ] TLS terminated at the proxy;
proxy_headers_middlewareconfigured.