diff --git a/docs/content/guides/docker.md b/docs/content/guides/docker.md new file mode 100644 index 00000000..036b38b1 --- /dev/null +++ b/docs/content/guides/docker.md @@ -0,0 +1,339 @@ +# Docker Deployment + +Running Gunicorn in Docker containers is the most common deployment pattern +for modern Python applications. This guide covers best practices for +containerizing Gunicorn applications. + +## Basic Dockerfile + +```dockerfile +FROM python:3.12-slim + +WORKDIR /app + +# Install dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application +COPY . . + +# Run gunicorn +CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:8000"] +``` + +Build and run: + +```bash +docker build -t myapp . +docker run -p 8000:8000 myapp +``` + +## Production Configuration + +### Environment Variables + +Use environment variables for configuration: + +```dockerfile +FROM python:3.12-slim + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +# Configuration via environment +ENV GUNICORN_WORKERS=4 +ENV GUNICORN_BIND=0.0.0.0:8000 + +CMD gunicorn app:app \ + --workers ${GUNICORN_WORKERS} \ + --bind ${GUNICORN_BIND} +``` + +Or use `GUNICORN_CMD_ARGS`: + +```dockerfile +ENV GUNICORN_CMD_ARGS="--workers=4 --bind=0.0.0.0:8000" +CMD ["gunicorn", "app:app"] +``` + +### Worker Count + +In containers, determine workers based on available CPU: + +```python +# gunicorn.conf.py +import multiprocessing + +workers = multiprocessing.cpu_count() * 2 + 1 +bind = "0.0.0.0:8000" +``` + +Or let Kubernetes/Docker limit CPU and calculate accordingly: + +```bash +# At runtime +gunicorn app:app --workers $(( 2 * $(nproc) + 1 )) +``` + +### Non-Root User + +Run as a non-root user for security: + +```dockerfile +FROM python:3.12-slim + +# Create non-root user +RUN useradd --create-home appuser +WORKDIR /home/appuser/app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY --chown=appuser:appuser . . + +USER appuser + +CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:8000"] +``` + +### Health Checks + +Add a health check endpoint and Docker health check: + +```dockerfile +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:8000/health || exit 1 +``` + +## Multi-Stage Build + +Reduce image size with multi-stage builds: + +```dockerfile +# Build stage +FROM python:3.12 AS builder + +WORKDIR /app +COPY requirements.txt . +RUN pip wheel --no-cache-dir --wheel-dir /wheels -r requirements.txt + +# Runtime stage +FROM python:3.12-slim + +WORKDIR /app + +# Copy wheels and install +COPY --from=builder /wheels /wheels +RUN pip install --no-cache-dir /wheels/* && rm -rf /wheels + +COPY . . + +CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:8000", "--workers", "4"] +``` + +## Docker Compose + +Example `docker-compose.yml`: + +```yaml +services: + web: + build: . + ports: + - "8000:8000" + environment: + - DATABASE_URL=postgres://db:5432/myapp + depends_on: + - db + deploy: + resources: + limits: + cpus: '2' + memory: 512M + + db: + image: postgres:15 + environment: + - POSTGRES_DB=myapp + - POSTGRES_PASSWORD=secret + volumes: + - postgres_data:/var/lib/postgresql/data + + nginx: + image: nginx:alpine + ports: + - "80:80" + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf:ro + depends_on: + - web + +volumes: + postgres_data: +``` + +## Kubernetes Deployment + +Example Kubernetes deployment: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: myapp +spec: + replicas: 3 + selector: + matchLabels: + app: myapp + template: + metadata: + labels: + app: myapp + spec: + containers: + - name: myapp + image: myapp:latest + ports: + - containerPort: 8000 + env: + - name: GUNICORN_WORKERS + value: "4" + resources: + limits: + cpu: "1" + memory: "512Mi" + requests: + cpu: "500m" + memory: "256Mi" + livenessProbe: + httpGet: + path: /health + port: 8000 + initialDelaySeconds: 10 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /health + port: 8000 + initialDelaySeconds: 5 + periodSeconds: 5 +--- +apiVersion: v1 +kind: Service +metadata: + name: myapp +spec: + selector: + app: myapp + ports: + - port: 80 + targetPort: 8000 +``` + +## Graceful Shutdown + +Gunicorn handles `SIGTERM` gracefully by default. Configure the timeout: + +```dockerfile +CMD ["gunicorn", "app:app", \ + "--bind", "0.0.0.0:8000", \ + "--graceful-timeout", "30", \ + "--timeout", "120"] +``` + +Match Docker's stop timeout: + +```yaml +# docker-compose.yml +services: + web: + stop_grace_period: 30s +``` + +## Logging + +Log to stdout/stderr for Docker log collection: + +```python +# gunicorn.conf.py +accesslog = "-" +errorlog = "-" +loglevel = "info" +``` + +Use JSON logging for log aggregation: + +```python +# gunicorn.conf.py +import json +import datetime + +class JsonFormatter: + def format(self, record): + return json.dumps({ + "timestamp": datetime.datetime.utcnow().isoformat(), + "level": record.levelname, + "message": record.getMessage(), + }) + +logconfig_dict = { + "version": 1, + "formatters": { + "json": {"()": JsonFormatter} + }, + "handlers": { + "console": { + "class": "logging.StreamHandler", + "formatter": "json", + "stream": "ext://sys.stdout" + } + }, + "root": { + "handlers": ["console"], + "level": "INFO" + } +} +``` + +## Troubleshooting + +### Worker Timeout + +If workers are killed with `[CRITICAL] WORKER TIMEOUT`, increase the timeout: + +```bash +gunicorn app:app --timeout 120 +``` + +Or investigate slow requests in your application. + +### Out of Memory + +If containers are OOM-killed: + +1. Reduce worker count +2. Use `--max-requests` to restart workers periodically +3. Increase container memory limits + +```bash +gunicorn app:app --workers 2 --max-requests 1000 --max-requests-jitter 100 +``` + +### Connection Reset + +If you see connection resets, ensure: + +1. Load balancer health checks match your `/health` endpoint +2. Graceful timeout is sufficient for in-flight requests +3. Keepalive settings match between Gunicorn and upstream proxy + +## See Also + +- [Deploy](../deploy.md) - General deployment patterns +- [Settings](../reference/settings.md) - All configuration options diff --git a/docs/content/install.md b/docs/content/install.md index df69e64f..27c73fed 100644 --- a/docs/content/install.md +++ b/docs/content/install.md @@ -3,140 +3,174 @@ !!! note Gunicorn requires **Python 3.12 or newer**. +## Quick Install +=== "pip" + + ```bash + pip install gunicorn + ``` + +=== "pipx" + + ```bash + pipx install gunicorn + ``` + +=== "Docker" + + ```bash + docker run -p 8000:8000 -v $(pwd):/app -w /app \ + python:3.12-slim sh -c "pip install gunicorn && gunicorn app:app" + ``` + + See the [Docker guide](guides/docker.md) for production configurations. + +=== "System Packages" + + **Debian/Ubuntu:** + ```bash + sudo apt-get update + sudo apt-get install gunicorn + ``` + + **Fedora:** + ```bash + sudo dnf install python3-gunicorn + ``` + + **Arch Linux:** + ```bash + sudo pacman -S gunicorn + ``` + + !!! warning + System packages may lag behind the latest release. For production, + prefer pip installation in a virtual environment. + +## Virtual Environment (Recommended) + +Always install Gunicorn inside a virtual environment to isolate dependencies: ```bash +# Create virtual environment +python -m venv venv + +# Activate it +source venv/bin/activate # Linux/macOS +# or: venv\Scripts\activate # Windows + +# Install gunicorn pip install gunicorn ``` -## From source +## From Source -Install Gunicorn from GitHub if you want the latest development version: +Install the latest development version from GitHub: ```bash pip install git+https://github.com/benoitc/gunicorn.git ``` -Stay current by upgrading in place: +Upgrade to the latest commit: ```bash pip install -U git+https://github.com/benoitc/gunicorn.git ``` -## Async workers +## Extra Packages -Install Eventlet or Gevent if your application benefits from cooperative I/O. -Both rely on `greenlet`, so make sure the Python headers are available (for -example, install the `python-dev` package on Ubuntu). - -```bash -pip install greenlet # Required for both -pip install eventlet # For eventlet workers -pip install gunicorn[eventlet] # Or, using extra -pip install gevent # For gevent workers -pip install gunicorn[gevent] # Or, using extra -``` - -!!! note - Gevent also needs `libevent` 1.4.x or 2.0.4+. Install it from your package - manager or build it manually if the packaged version is too old. - - - -## Extra packages - -Some Gunicorn options require additional dependencies. Install them via -extras to pull everything in with one command. - -Most extras enable alternative worker types—see the -[design docs](design.md) for when each worker makes sense. - -- `gunicorn[eventlet]` — Eventlet-based greenlet workers -- `gunicorn[gevent]` — Gevent-based greenlet workers -- `gunicorn[gthread]` — Threaded workers -- `gunicorn[tornado]` — Tornado-based workers (not recommended) - -If you run more than one Gunicorn instance, the -[`proc_name`](reference/settings.md#proc_name) setting helps distinguish them in tools such -as `ps` and `top`. - -- `gunicorn[setproctitle]` — Enables setting the process name - -You can combine multiple extras, for example: +Gunicorn provides optional extras for additional worker types and features. +Install them with pip's bracket syntax: ```bash pip install gunicorn[gevent,setproctitle] ``` -## Debian GNU/Linux +### Worker Types -On Debian systems prefer the distribution packages unless you need per-project -virtual environments: +| Extra | Description | +|-------|-------------| +| `gunicorn[eventlet]` | Eventlet-based greenlet workers | +| `gunicorn[gevent]` | Gevent-based greenlet workers | +| `gunicorn[gthread]` | Threaded workers | +| `gunicorn[tornado]` | Tornado-based workers (not recommended) | -- Zero-effort installation: automatically starts multiple instances based on - configs in `/etc/gunicorn.d`. -- Sensible log locations (`/var/log/gunicorn`) with `logrotate` support. -- Improved security: run each instance with a dedicated UNIX user/group. -- Safe upgrades: minimal downtime, reversible changes, and easy package purge. +See the [design docs](design.md) for guidance on choosing worker types. -### stable ("buster") +### Utilities -The Debian [stable](https://www.debian.org/releases/stable/) release ships -Gunicorn 19.9.0 (December 2020): +| Extra | Description | +|-------|-------------| +| `gunicorn[setproctitle]` | Set process name in `ps`/`top` output | + +!!! tip + If running multiple Gunicorn instances, use `setproctitle` with the + [`proc_name`](reference/settings.md#proc_name) setting to distinguish them. + +## Async Workers + +For applications using async I/O patterns, install the appropriate greenlet +library: + +=== "Gevent" + + ```bash + pip install gunicorn[gevent] + ``` + + Run with: + ```bash + gunicorn app:app --worker-class gevent + ``` + +=== "Eventlet" + + ```bash + pip install gunicorn[eventlet] + ``` + + Run with: + ```bash + gunicorn app:app --worker-class eventlet + ``` + +=== "ASGI (asyncio)" + + No extra installation required: + + ```bash + gunicorn app:app --worker-class asgi + ``` + + For better performance, install uvloop: + ```bash + pip install uvloop + gunicorn app:app --worker-class asgi --asgi-loop uvloop + ``` + +!!! note + Greenlet-based workers require the Python development headers. On Ubuntu: + `sudo apt-get install python3-dev` + +## Verify Installation + +Check the installed version: ```bash -sudo apt-get install gunicorn3 +gunicorn --version ``` -Install Gunicorn 20.0.4 from [Debian Backports](https://backports.debian.org/) -by adding this line to `/etc/apt/sources.list`: - -```text -deb http://ftp.debian.org/debian buster-backports main -``` - -Refresh package metadata and install: +Test with a simple application: ```bash -sudo apt-get update -sudo apt-get -t buster-backports install gunicorn +echo 'def app(e, s): s("200 OK", []); return [b"OK"]' > test_app.py +gunicorn test_app:app +# Visit http://127.0.0.1:8000 ``` -### oldstable ("stretch") +## Next Steps -Stretch provides Gunicorn 19.6.0 (December 2020). Install the Python 3 version: - -```bash -sudo apt-get install gunicorn3 -``` - -To upgrade to 19.7.1 from backports, add: - -```text -deb http://ftp.debian.org/debian stretch-backports main -``` - -Then update and install: - -```bash -sudo apt-get update -sudo apt-get -t stretch-backports install gunicorn3 -``` - -### testing ("bullseye") and unstable ("sid") - -Both distributions include Gunicorn 20.0.4. Install it in the usual way: - -```bash -sudo apt-get install gunicorn -``` - -## Ubuntu - -Ubuntu 20.04 LTS (Focal Fossa) and newer include Gunicorn 20.0.4. Keep it -current through the package manager: - -```bash -sudo apt-get update -sudo apt-get install gunicorn -``` +- [Quickstart](quickstart.md) - Get running in 5 minutes +- [Run](run.md) - CLI usage and framework integration +- [Configure](configure.md) - Configuration options diff --git a/docs/content/quickstart.md b/docs/content/quickstart.md new file mode 100644 index 00000000..5457483a --- /dev/null +++ b/docs/content/quickstart.md @@ -0,0 +1,115 @@ +# Quickstart + +Get a Python web application running with Gunicorn in 5 minutes. + +## Install + +```bash +pip install gunicorn +``` + +## Create an Application + +Create `app.py`: + +=== "Flask" + + ```python + from flask import Flask + + app = Flask(__name__) + + @app.route("/") + def hello(): + return "Hello, World!" + ``` + +=== "FastAPI" + + ```python + from fastapi import FastAPI + + app = FastAPI() + + @app.get("/") + def hello(): + return {"message": "Hello, World!"} + ``` + +=== "Django" + + Django projects already have a WSGI application at `myproject/wsgi.py`. + No additional code is needed. + +=== "Plain WSGI" + + ```python + def app(environ, start_response): + data = b"Hello, World!" + start_response("200 OK", [ + ("Content-Type", "text/plain"), + ("Content-Length", str(len(data))) + ]) + return [data] + ``` + +## Run + +```bash +gunicorn app:app +``` + +For Django: + +```bash +gunicorn myproject.wsgi +``` + +For FastAPI (ASGI): + +```bash +gunicorn app:app --worker-class asgi +``` + +## Add Workers + +Use multiple workers to handle concurrent requests: + +```bash +gunicorn app:app --workers 4 +``` + +A good starting point is `2 * CPU_CORES + 1` workers. + +## Bind to a Port + +By default Gunicorn binds to `127.0.0.1:8000`. Change it with: + +```bash +gunicorn app:app --bind 0.0.0.0:8080 +``` + +## Configuration File + +Create `gunicorn.conf.py` for reusable settings: + +```python +bind = "0.0.0.0:8000" +workers = 4 +accesslog = "-" +``` + +Then run: + +```bash +gunicorn app:app +``` + +Gunicorn automatically loads `gunicorn.conf.py` from the current directory. + +## Next Steps + +- [Run](run.md) - Full CLI reference and framework integration +- [Configure](configure.md) - Configuration file options +- [Deploy](deploy.md) - Production deployment with nginx and process managers +- [Settings](reference/settings.md) - Complete settings reference diff --git a/mkdocs.yml b/mkdocs.yml index 6a5caf1a..2cf23a68 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -7,19 +7,23 @@ use_directory_urls: true nav: - Home: index.md - - Guides: + - Getting Started: + - Quickstart: quickstart.md - Install: install.md - Run: run.md - Configure: configure.md + - Guides: - Deploy: deploy.md + - Docker: guides/docker.md - ASGI Worker: asgi.md - uWSGI Protocol: uwsgi.md - Signals: signals.md - Instrumentation: instrumentation.md - Custom: custom.md - - Community: community.md - - FAQ: faq.md - Design: design.md + - Community: + - Overview: community.md + - FAQ: faq.md - Reference: - Settings: reference/settings.md - News: