# The uvicorn-gunicorn image is deprecated because Uvicorn learned to manage workers

> tiangolo/uvicorn-gunicorn-docker published Docker images that ran Uvicorn workers under Gunicorn with worker counts auto-tuned to the available CPU cores, and the project now tells you plainly not to use it, because Uvicorn handles subprocesses and restarts dead workers itself. The repository is still maintained and the last push was on 2026-10-16, but the newest release is 0.8.0 from 2024-03-18 and the image survives mainly for simple single-host deployments.

**tiangolo/uvicorn-gunicorn-docker** — Docker image with Uvicorn managed by Gunicorn for high-performance web applications in Python with performance auto-tuning.

- Repository: https://github.com/tiangolo/uvicorn-gunicorn-docker
- Stars: 639 · Forks: 245
- Language: Python
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/tiangolo-uvicorn-gunicorn-docker

## Why the project says you probably do not need this image

The README opens with a deprecation notice, and it is blunt about the conclusion. This Docker image is now deprecated, there is no need to use it, and you can just use Uvicorn with --workers. The text that follows explains the reasoning, and it is a good example of a maintainer documenting a decision rather than quietly drifting.

The technical reason sits in the Technical Details section. Uvicorn did not have support for managing worker processing, including restarting dead workers, but now it does. Before that, Gunicorn could be used as a process manager running Uvicorn workers, and that added complexity which is no longer necessary.

That is the whole argument. The image existed to fill a gap in Uvicorn, and the gap closed. The rest of the document is kept for historical reasons, as the README says explicitly, and it points readers to the FastAPI documentation on building Docker images for deployments instead.

The other warning in the README is aimed at cluster users. If you run Kubernetes, Docker Swarm Mode, Nomad or something similar, handling replication at the cluster level is usually the right move rather than running a process manager inside each container, which is exactly what this image does. In those cases the README suggests building an image from scratch and running a single Uvicorn process.

The repository is not archived and the last push was on 2026-10-16, so the guidance will keep being corrected, but the newest release is still 0.8.0 from 2024-03-18.

## What the image did under the hood

The legacy section explains the design. The image set a sensible configuration based on the server it was running on, which the README describes as the amount of CPU cores available, and it did that without making sacrifices. Configuration could be overridden with environment variables or by replacing the configuration files.

The auto-tuning mechanism is the distinctive part. Rather than you choosing a worker count, the image started a number of worker processes based on the available CPU cores, so you could add your code and get high performance automatically, which the README ties to simple deployments.

The motivation for the whole approach was performance. The README notes that Python web applications running with Uvicorn, using the ASGI specification for asynchronous Python web applications, have shown some of the best performances as measured by third-party benchmarks, and that the achievable performance is on par with or in many cases superior to Go and Node.js frameworks. Those are external measurements rather than numbers produced here, and the benchmark link is included so you can look at the methodology yourself.

There was a slim variant, and the image was created as the base for two sibling images, tiangolo/uvicorn-gunicorn-starlette and tiangolo/uvicorn-gunicorn-fastapi, though the README notes it could be used as a base for any Python web application.

## Supported tags are 3.10 and 3.11, with date-pinned builds for the rest

The image tags are the practical detail for anyone who still has one in a pipeline.

The supported set is small: python3.11 and latest, python3.10, and the slim variants python3.11-slim and python3.10-slim, each with a corresponding Dockerfile in the docker-images/ directory of the repository. Everything older is listed as deprecated and no longer supported or maintained, and the README notes that those images are removed from the GitHub repository although the last versions pushed might still be available on Docker Hub for anyone who was pulling them.

The deprecated list covers python3.9 with its slim and alpine3.14 variants, python3.8 with slim and alpine3.10, python3.7 with slim and alpine3.8, and python3.6 with slim and alpine3.8.

For reproducibility there is a tag per build date, and the README gives a concrete example of pinning with tiangolo/uvicorn-gunicorn:python3.11-2024-11-02. The last date tags for the deprecated versions are also listed, running from python3.9-2025-11-09 down to python3.6-2022-11-25, which is a useful way to see how long each line of the image was still being built.

Those date tags matter for anyone with an old image in a running deployment. They tell you exactly which build you have, and they let you decide whether to stay on it while you migrate.

## The Kubernetes replacement is one Uvicorn process

The README's recommended alternative for a cluster deployment is a Dockerfile built from scratch. The example in the document is short enough to be the whole recipe:

```dockerfile
FROM python:3.11

WORKDIR /code

COPY ./requirements.txt /code/requirements.txt

RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt

COPY ./app /code/app

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"]
```

The differences from the deprecated image are all deliberate. There is no Gunicorn, so there is no process manager inside the container and no worker count to choose. The container runs a single Uvicorn process listening on all interfaces on port 80, and the dependencies are installed from a requirements file in the usual three-step copy, install, copy pattern.

The README is explicit that the same process and ideas apply to other ASGI frameworks, not just FastAPI, since the container here is generic Python with an ASGI application inside it.

The argument for this shape is about where replication lives. A cluster scheduler already restarts containers, spreads them across nodes and replaces unhealthy ones, so a process manager inside each container duplicates work the scheduler does, and duplicates it badly, because a container that supervises its own workers is opaque to the scheduler's health checks.

## For several workers in one container, Uvicorn now handles it

The other case is a single container that genuinely needs several workers. The README says that if you definitely want multiple workers on a single container, Uvicorn now supports handling subprocesses including restarting dead ones, so there is no need for Gunicorn to manage multiple workers inside a container.

The change to the example is a single extra option:

```dockerfile
FROM python:3.11

WORKDIR /code

COPY ./requirements.txt /code/requirements.txt

RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt

COPY ./app /code/app

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80", "--workers", "4"]
```

Everything else is identical, and the number of workers is now an explicit argument you pass rather than something derived from the CPU count of the machine.

That last difference is the one to think about. The old image sized its pool from the host, so the same image behaved differently on a laptop and on a 16 core server, and the same container behaved differently when a scheduler gave it different CPU limits. Setting the count yourself means the configuration is the same everywhere, which is what you want in a deployment that is scaled by replication count.

The README's conclusion is that this is all you need and that you do not need the deprecated image at all.

## Where the deprecated image still makes sense

There is one audience left for the image, and it is worth naming so the deprecation notice is not read as a removal.

The image was built for simple deployments, which is the phrase the README uses for the auto-tuning benefit. If you run a single virtual machine, a small VPS or a compose file, and you want several workers without thinking about how many, the image still does that in one pull. The configuration was tuned to the host, environment variables and configuration file overrides were supported, and the slim variants existed for people who cared about image size.

The cost of staying on it is upgrade debt. The supported tags cover Python 3.10 and 3.11, the newest release is from March 2024, and the pull request and issue flow for the image is no longer the focus of the maintainer. The instructions to build your own image are short, so the cost of migrating is low, which is exactly why the maintainer would rather you migrate.

A second, quieter cost is the process manager inside the container. Even in a single-host deployment, Gunicorn supervising Uvicorn means two processes to configure, two sets of signals to get right and one more component in the failure path. The historical argument for it was that Uvicorn could not restart dead workers, and that argument no longer holds.

So the honest summary is that the image is not broken, it is just unnecessary. Deprecated and still pulling is a state that resolves itself the first time you need a Python version it does not have, and that is a good moment to move to a plain base image with an explicit --workers value.

## Licence, packaging and what the repository still contains

The project is MIT licensed, with the licence file at the root of the repository. The repository itself is small, and its shape reflects what the project now is: a .github directory holding the test and deploy workflows, the docker-images/ directory with the per-version Dockerfiles, a tests/ directory, a release-notes.md file and the README that documents both the deprecated path and its replacement.

That test and deploy workflow pair is worth noting, because it explains how the Docker Hub images were produced and how the date tags came to exist. Each build was pushed under a tag carrying its build date, which is what gives you the pinning option the README recommends.

For anyone maintaining a deployment on one of these images, the concrete steps are short. Find the date tag that matches the build you are running, note the Python version it carries, and plan the move to a python:3.11 base image with the --workers option set explicitly. The difference in behaviour to test for is shutdown and restart, since a supervised pool and a single process handle signals differently, and health checks that assumed a fixed worker set may need adjusting.

The sister images, uvicorn-gunicorn-starlette and uvicorn-gunicorn-fastapi, follow the same deprecation path, so a migration should cover whichever of the three your stack actually uses.

## Conclusion

Adopt the uvicorn-gunicorn image only for a simple single-host deployment where a Gunicorn process manager is what you already operate, and pin a date tag if you use it at all. Do not adopt it for Kubernetes, Docker Swarm or Nomad deployments, where the README argues replication belongs at the cluster level and each container should run one Uvicorn process. Verify first by replacing the image with a plain python:3.11 base and the --workers option, then checking that your health checks and signal handling still work, since a single process and a supervised pool behave differently at shutdown.

## FAQ

### Is the tiangolo uvicorn-gunicorn Docker image still maintained?

The image is deprecated, and the README says there is no need to use it because Uvicorn can now be used with --workers, since Uvicorn supports managing subprocesses and restarting dead workers. The repository is not archived and the last push was on 2026-10-16, but the newest release is 0.8.0 from 2024-03-18.

### How do I run several Uvicorn workers in one container now?

Add the --workers option to the Uvicorn command in your Dockerfile, for example CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80", "--workers", "4"]. The README presents this as the replacement for Gunicorn managing Uvicorn workers inside a single container.

### Which tags of the uvicorn-gunicorn image are still supported?

The supported tags are python3.11, latest, python3.10, python3.11-slim and python3.10-slim. The python3.6 to python3.9 tags are deprecated and removed from the repository, although the last pushed versions may still exist on Docker Hub, and tags exist for each build date so an image can be pinned, for example tiangolo/uvicorn-gunicorn:python3.11-2024-11-02.

## Sources

- [Issues](https://github.com/tiangolo/uvicorn-gunicorn-docker/issues)
- [License: MIT](https://github.com/tiangolo/uvicorn-gunicorn-docker/blob/master/LICENSE)
- [README](https://github.com/tiangolo/uvicorn-gunicorn-docker/blob/master/README.md)
- [Releases](https://github.com/tiangolo/uvicorn-gunicorn-docker/releases)
- [tiangolo/uvicorn-gunicorn-docker on GitHub](https://github.com/tiangolo/uvicorn-gunicorn-docker)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/tiangolo-uvicorn-gunicorn-docker
