├── tests ├── __init__.py ├── test_01_main │ ├── __init__.py │ └── test_defaults.py └── utils.py ├── .github ├── FUNDING.yml ├── labeler.yml ├── dependabot.yml ├── workflows │ ├── latest-changes.yml │ ├── labeler.yml │ ├── issue-manager.yml │ ├── test.yml │ └── deploy.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ └── privileged.yml └── DISCUSSION_TEMPLATE │ └── questions.yml ├── docker-images ├── requirements.txt ├── python3.10.dockerfile ├── python3.11.dockerfile ├── python3.10-slim.dockerfile ├── python3.11-slim.dockerfile └── app │ └── main.py ├── LICENSE ├── SECURITY.md ├── .gitignore └── README.md /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/test_01_main/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [tiangolo] 2 | -------------------------------------------------------------------------------- /docker-images/requirements.txt: -------------------------------------------------------------------------------- 1 | uvicorn[standard]==0.38.0 2 | gunicorn==23.0.0 3 | fastapi[all]==0.121.0 4 | -------------------------------------------------------------------------------- /docker-images/python3.10.dockerfile: -------------------------------------------------------------------------------- 1 | FROM tiangolo/uvicorn-gunicorn:python3.10 2 | 3 | LABEL maintainer="Sebastian Ramirez " 4 | 5 | COPY requirements.txt /tmp/requirements.txt 6 | RUN pip install --no-cache-dir -r /tmp/requirements.txt 7 | 8 | COPY ./app /app 9 | -------------------------------------------------------------------------------- /docker-images/python3.11.dockerfile: -------------------------------------------------------------------------------- 1 | FROM tiangolo/uvicorn-gunicorn:python3.11 2 | 3 | LABEL maintainer="Sebastian Ramirez " 4 | 5 | COPY requirements.txt /tmp/requirements.txt 6 | RUN pip install --no-cache-dir -r /tmp/requirements.txt 7 | 8 | COPY ./app /app 9 | -------------------------------------------------------------------------------- /docker-images/python3.10-slim.dockerfile: -------------------------------------------------------------------------------- 1 | FROM tiangolo/uvicorn-gunicorn:python3.10-slim 2 | 3 | LABEL maintainer="Sebastian Ramirez " 4 | 5 | COPY requirements.txt /tmp/requirements.txt 6 | RUN pip install --no-cache-dir -r /tmp/requirements.txt 7 | 8 | COPY ./app /app 9 | -------------------------------------------------------------------------------- /docker-images/python3.11-slim.dockerfile: -------------------------------------------------------------------------------- 1 | FROM tiangolo/uvicorn-gunicorn:python3.11-slim 2 | 3 | LABEL maintainer="Sebastian Ramirez " 4 | 5 | COPY requirements.txt /tmp/requirements.txt 6 | RUN pip install --no-cache-dir -r /tmp/requirements.txt 7 | 8 | COPY ./app /app 9 | -------------------------------------------------------------------------------- /docker-images/app/main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from fastapi import FastAPI 4 | 5 | version = f"{sys.version_info.major}.{sys.version_info.minor}" 6 | 7 | app = FastAPI() 8 | 9 | 10 | @app.get("/") 11 | async def read_root(): 12 | message = f"Hello world! From FastAPI running on Uvicorn with Gunicorn. Using Python {version}" 13 | return {"message": message} 14 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | internal: 2 | - all: 3 | - changed-files: 4 | - any-glob-to-any-file: 5 | - .github/** 6 | - .gitignore 7 | - all-globs-to-all-files: 8 | - '!docker-images/requirements.txt' 9 | 10 | upgrade: 11 | - all: 12 | - changed-files: 13 | - any-glob-to-any-file: 14 | - docker-images/requirements.txt 15 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # GitHub Actions 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "daily" 8 | commit-message: 9 | prefix: ⬆ 10 | # Python 11 | - package-ecosystem: "pip" 12 | directory: "/" 13 | schedule: 14 | interval: "daily" 15 | commit-message: 16 | prefix: ⬆ 17 | -------------------------------------------------------------------------------- /.github/workflows/latest-changes.yml: -------------------------------------------------------------------------------- 1 | name: Latest Changes 2 | 3 | on: 4 | pull_request_target: 5 | branches: 6 | - master 7 | types: 8 | - closed 9 | workflow_dispatch: 10 | inputs: 11 | number: 12 | description: PR number 13 | required: true 14 | 15 | jobs: 16 | latest-changes: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v6 20 | with: 21 | # To allow latest-changes to commit to master 22 | token: ${{ secrets.UVICORN_GUNICORN_FASTAPI_DOCKER_LATEST_CHANGES }} 23 | - uses: tiangolo/latest-changes@0.4.1 24 | with: 25 | token: ${{ secrets.GITHUB_TOKEN }} 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Security Contact 4 | about: Please report security vulnerabilities to security@tiangolo.com 5 | - name: Question or Problem 6 | about: Ask a question or ask about a problem in GitHub Discussions. 7 | url: https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/discussions/categories/questions 8 | - name: Feature Request 9 | about: To suggest an idea or ask about a feature, please start with a question saying what you would like to achieve. There might be a way to do it already. 10 | url: https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/discussions/categories/questions 11 | -------------------------------------------------------------------------------- /.github/workflows/labeler.yml: -------------------------------------------------------------------------------- 1 | name: Labels 2 | on: 3 | pull_request_target: 4 | types: 5 | - opened 6 | - synchronize 7 | - reopened 8 | # For label-checker 9 | - labeled 10 | - unlabeled 11 | 12 | jobs: 13 | labeler: 14 | permissions: 15 | contents: read 16 | pull-requests: write 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/labeler@v6 20 | if: ${{ github.event.action != 'labeled' && github.event.action != 'unlabeled' }} 21 | - run: echo "Done adding labels" 22 | # Run this after labeler applied labels 23 | check-labels: 24 | needs: 25 | - labeler 26 | permissions: 27 | pull-requests: read 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: docker://agilepathway/pull-request-label-checker:latest 31 | with: 32 | one_of: breaking,security,feature,bug,refactor,upgrade,docs,internal 33 | repo_token: ${{ secrets.GITHUB_TOKEN }} 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/privileged.yml: -------------------------------------------------------------------------------- 1 | name: Privileged 2 | description: You are @tiangolo or he asked you directly to create an issue here. If not, check the other options. 👇 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | Thanks for your interest in this project! 🚀 8 | 9 | If you are not @tiangolo or he didn't ask you directly to create an issue here, please start the conversation in a [Question in GitHub Discussions](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/discussions/categories/questions) instead. 10 | - type: checkboxes 11 | id: privileged 12 | attributes: 13 | label: Privileged issue 14 | description: Confirm that you are allowed to create an issue here. 15 | options: 16 | - label: I'm @tiangolo or he asked me directly to create an issue here. 17 | required: true 18 | - type: textarea 19 | id: content 20 | attributes: 21 | label: Issue Content 22 | description: Add the content of the issue here. 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Sebastián Ramírez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/issue-manager.yml: -------------------------------------------------------------------------------- 1 | name: Issue Manager 2 | 3 | on: 4 | schedule: 5 | - cron: "15 17 * * *" 6 | issue_comment: 7 | types: 8 | - created 9 | issues: 10 | types: 11 | - labeled 12 | pull_request_target: 13 | types: 14 | - labeled 15 | workflow_dispatch: 16 | 17 | permissions: 18 | issues: write 19 | pull-requests: write 20 | 21 | jobs: 22 | issue-manager: 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: tiangolo/issue-manager@0.6.0 26 | with: 27 | token: ${{ secrets.GITHUB_TOKEN }} 28 | config: > 29 | { 30 | "answered": { 31 | "delay": 864000, 32 | "message": "Assuming the original need was handled, this will be automatically closed now. But feel free to add more comments or create new issues or PRs." 33 | }, 34 | "waiting": { 35 | "delay": 2628000, 36 | "message": "As this PR has been waiting for the original user for a while but seems to be inactive, it's now going to be closed. But if there's anyone interested, feel free to create a new PR." 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | Security is very important for this project and its community. 🔒 4 | 5 | Learn more about it below. 👇 6 | 7 | ## Versions 8 | 9 | The latest version or release is supported. 10 | 11 | You are encouraged to write tests for your application and update your versions frequently after ensuring that your tests are passing. This way you will benefit from the latest features, bug fixes, and **security fixes**. 12 | 13 | ## Reporting a Vulnerability 14 | 15 | If you think you found a vulnerability, and even if you are not sure about it, please report it right away by sending an email to: security@tiangolo.com. Please try to be as explicit as possible, describing all the steps and example code to reproduce the security issue. 16 | 17 | I (the author, [@tiangolo](https://twitter.com/tiangolo)) will review it thoroughly and get back to you. 18 | 19 | ## Public Discussions 20 | 21 | Please restrain from publicly discussing a potential security vulnerability. 🙊 22 | 23 | It's better to discuss privately and try to find a solution first, to limit the potential impact as much as possible. 24 | 25 | --- 26 | 27 | Thanks for your help! 28 | 29 | The community and I thank you for that. 🙇 30 | -------------------------------------------------------------------------------- /tests/utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | from typing import Any, Dict, List 4 | 5 | from docker.client import DockerClient 6 | from docker.errors import NotFound 7 | from docker.models.containers import Container 8 | 9 | CONTAINER_NAME = "uvicorn-gunicorn-fastapi-test" 10 | 11 | 12 | def get_process_names(container: Container) -> List[str]: 13 | top = container.top() 14 | process_commands = [p[7] for p in top["Processes"]] 15 | gunicorn_processes = [p for p in process_commands if "gunicorn" in p] 16 | return gunicorn_processes 17 | 18 | 19 | def get_gunicorn_conf_path(container: Container) -> str: 20 | gunicorn_processes = get_process_names(container) 21 | first_process = gunicorn_processes[0] 22 | first_part, partition, last_part = first_process.partition("-c") 23 | gunicorn_conf = last_part.strip().split()[0] 24 | return gunicorn_conf 25 | 26 | 27 | def get_config(container: Container) -> Dict[str, Any]: 28 | gunicorn_conf = get_gunicorn_conf_path(container) 29 | result = container.exec_run(f"python {gunicorn_conf}") 30 | return json.loads(result.output.decode()) 31 | 32 | 33 | def remove_previous_container(client: DockerClient) -> None: 34 | try: 35 | previous = client.containers.get(CONTAINER_NAME) 36 | previous.stop() 37 | previous.remove() 38 | except NotFound: 39 | return None 40 | 41 | 42 | def get_logs(container: DockerClient) -> str: 43 | logs = container.logs() 44 | return logs.decode("utf-8") 45 | 46 | 47 | def get_response_text1() -> str: 48 | python_version = os.getenv("PYTHON_VERSION") 49 | return f"Hello world! From FastAPI running on Uvicorn with Gunicorn. Using Python {python_version}" 50 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | types: 9 | - opened 10 | - synchronize 11 | workflow_dispatch: 12 | schedule: 13 | # cron every week on monday 14 | - cron: "0 0 * * 1" 15 | 16 | jobs: 17 | test: 18 | strategy: 19 | matrix: 20 | image: 21 | - name: latest 22 | python_version: "3.11" 23 | - name: python3.11 24 | python_version: "3.11" 25 | - name: python3.10 26 | python_version: "3.10" 27 | - name: python3.11-slim 28 | python_version: "3.11" 29 | - name: python3.10-slim 30 | python_version: "3.10" 31 | fail-fast: true 32 | runs-on: ubuntu-latest 33 | steps: 34 | - uses: actions/checkout@v5 35 | - name: Set Dockerfile name 36 | if: matrix.image.name != 'latest' 37 | run: echo "DOCKERFILE_NAME=${{ matrix.image.name }}" >> $GITHUB_ENV 38 | - name: Set Dockerfile name latest 39 | if: matrix.image.name == 'latest' 40 | run: echo "DOCKERFILE_NAME=python${{ matrix.image.python_version }}" >> $GITHUB_ENV 41 | - name: Build 42 | uses: docker/build-push-action@v6 43 | with: 44 | push: false 45 | tags: tiangolo/uvicorn-gunicorn-fastapi:${{ matrix.image.name }} 46 | context: ./docker-images/ 47 | file: ./docker-images/${{ env.DOCKERFILE_NAME }}.dockerfile 48 | - name: Set up Python 49 | uses: actions/setup-python@v6 50 | with: 51 | python-version: "3.10" 52 | - name: Install Dependencies 53 | run: python -m pip install docker pytest 54 | - name: Test Image 55 | run: pytest tests 56 | env: 57 | NAME: ${{ matrix.image.name }} 58 | PYTHON_VERSION: ${{ matrix.image.python_version }} 59 | check: 60 | if: always() 61 | needs: 62 | - test 63 | runs-on: ubuntu-latest 64 | steps: 65 | - name: Decide whether the needed jobs succeeded or failed 66 | uses: re-actors/alls-green@release/v1 67 | with: 68 | jobs: ${{ toJSON(needs) }} 69 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | workflow_dispatch: 8 | schedule: 9 | # cron every week on monday 10 | - cron: "0 0 * * 1" 11 | 12 | jobs: 13 | deploy: 14 | strategy: 15 | matrix: 16 | image: 17 | - name: latest 18 | python_version: "3.11" 19 | - name: python3.11 20 | python_version: "3.11" 21 | - name: python3.10 22 | python_version: "3.10" 23 | - name: python3.11-slim 24 | python_version: "3.11" 25 | - name: python3.10-slim 26 | python_version: "3.10" 27 | fail-fast: true 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@v5 31 | - name: Set Dockerfile name 32 | if: matrix.image.name != 'latest' 33 | run: echo "DOCKERFILE_NAME=${{ matrix.image.name }}" >> $GITHUB_ENV 34 | - name: Set Dockerfile name latest 35 | if: matrix.image.name == 'latest' 36 | run: echo "DOCKERFILE_NAME=python${{ matrix.image.python_version }}" >> $GITHUB_ENV 37 | - name: Set up Docker Buildx 38 | uses: docker/setup-buildx-action@v3 39 | - name: Login to DockerHub 40 | uses: docker/login-action@v3 41 | with: 42 | username: ${{ secrets.DOCKERHUB_USERNAME }} 43 | password: ${{ secrets.DOCKERHUB_TOKEN }} 44 | - name: Get date for tags 45 | run: echo "DATE_TAG=$(date -I)" >> "$GITHUB_ENV" 46 | - name: Build and push 47 | uses: docker/build-push-action@v6 48 | with: 49 | push: true 50 | platforms: linux/amd64,linux/arm64 51 | tags: | 52 | tiangolo/uvicorn-gunicorn-fastapi:${{ matrix.image.name }} 53 | tiangolo/uvicorn-gunicorn-fastapi:${{ matrix.image.name }}-${{ env.DATE_TAG }} 54 | context: ./docker-images/ 55 | file: ./docker-images/${{ env.DOCKERFILE_NAME }}.dockerfile 56 | - name: Docker Hub Description 57 | uses: peter-evans/dockerhub-description@v4 58 | with: 59 | username: ${{ secrets.DOCKERHUB_USERNAME }} 60 | password: ${{ secrets.DOCKERHUB_TOKEN }} 61 | repository: tiangolo/uvicorn-gunicorn-fastapi 62 | -------------------------------------------------------------------------------- /tests/test_01_main/test_defaults.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | 4 | import docker 5 | import requests 6 | from docker.client import DockerClient 7 | 8 | from ..utils import ( 9 | CONTAINER_NAME, 10 | get_config, 11 | get_logs, 12 | get_response_text1, 13 | remove_previous_container, 14 | ) 15 | 16 | client = docker.from_env() 17 | 18 | 19 | def verify_container(container: DockerClient, response_text: str) -> None: 20 | response = requests.get("http://127.0.0.1:8000") 21 | data = response.json() 22 | assert data["message"] == response_text 23 | config_data = get_config(container) 24 | assert config_data["workers_per_core"] == 1 25 | assert config_data["use_max_workers"] is None 26 | assert config_data["host"] == "0.0.0.0" 27 | assert config_data["port"] == "80" 28 | assert config_data["loglevel"] == "info" 29 | assert config_data["workers"] >= 2 30 | assert config_data["bind"] == "0.0.0.0:80" 31 | assert config_data["graceful_timeout"] == 120 32 | assert config_data["timeout"] == 120 33 | assert config_data["keepalive"] == 5 34 | assert config_data["errorlog"] == "-" 35 | assert config_data["accesslog"] == "-" 36 | logs = get_logs(container) 37 | assert "Checking for script in /app/prestart.sh" in logs 38 | assert "Running script /app/prestart.sh" in logs 39 | assert ( 40 | "Running inside /app/prestart.sh, you could add migrations to this file" in logs 41 | ) 42 | assert '"GET / HTTP/1.1" 200' in logs 43 | assert "[INFO] Application startup complete." in logs 44 | assert "Using worker: uvicorn.workers.UvicornWorker" in logs 45 | 46 | 47 | def test_defaults() -> None: 48 | name = os.getenv("NAME") 49 | image = f"tiangolo/uvicorn-gunicorn-fastapi:{name}" 50 | response_text = get_response_text1() 51 | sleep_time = int(os.getenv("SLEEP_TIME", 1)) 52 | remove_previous_container(client) 53 | container = client.containers.run( 54 | image, name=CONTAINER_NAME, ports={"80": "8000"}, detach=True 55 | ) 56 | time.sleep(sleep_time) 57 | verify_container(container, response_text) 58 | container.stop() 59 | # Test that everything works after restarting too 60 | container.start() 61 | time.sleep(sleep_time) 62 | verify_container(container, response_text) 63 | container.stop() 64 | container.remove() 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Python template 2 | # Byte-compiled / optimized / DLL files 3 | __pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | .mypy_cache 7 | .vscode 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | env/ 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *,cover 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | 57 | # Sphinx documentation 58 | docs/_build/ 59 | 60 | # PyBuilder 61 | target/ 62 | ### JetBrains template 63 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 64 | 65 | *.iml 66 | 67 | ## Directory-based project format: 68 | .idea/ 69 | # if you remove the above rule, at least ignore the following: 70 | 71 | # User-specific stuff: 72 | # .idea/workspace.xml 73 | # .idea/tasks.xml 74 | # .idea/dictionaries 75 | 76 | # Sensitive or high-churn files: 77 | # .idea/dataSources.ids 78 | # .idea/dataSources.xml 79 | # .idea/sqlDataSources.xml 80 | # .idea/dynamic.xml 81 | # .idea/uiDesigner.xml 82 | 83 | # Gradle: 84 | # .idea/gradle.xml 85 | # .idea/libraries 86 | 87 | # Mongo Explorer plugin: 88 | # .idea/mongoSettings.xml 89 | 90 | ## File-based project format: 91 | *.ipr 92 | *.iws 93 | 94 | ## Plugin-specific files: 95 | 96 | # IntelliJ 97 | /out/ 98 | 99 | # mpeltonen/sbt-idea plugin 100 | .idea_modules/ 101 | 102 | # JIRA plugin 103 | atlassian-ide-plugin.xml 104 | 105 | # Crashlytics plugin (for Android Studio and IntelliJ) 106 | com_crashlytics_export_strings.xml 107 | crashlytics.properties 108 | crashlytics-build.properties 109 | 110 | # Custom 111 | Pipfile.lock 112 | poetry.lock 113 | -------------------------------------------------------------------------------- /.github/DISCUSSION_TEMPLATE/questions.yml: -------------------------------------------------------------------------------- 1 | labels: [question] 2 | body: 3 | - type: markdown 4 | attributes: 5 | value: | 6 | Thanks for your interest in this project! 🚀 7 | 8 | Please follow these instructions, fill every question, and do every step. 🙏 9 | 10 | I'm asking this because answering questions and solving problems in GitHub is what consumes most of the time. 11 | 12 | I end up not being able to add new features, fix bugs, review pull requests, etc. as fast as I wish because I have to spend too much time handling questions. 13 | 14 | All that, on top of all the incredible help provided by a bunch of community members, that give a lot of their time to come here and help others. 15 | 16 | That's a lot of work, but if more users came to help others like them just a little bit more, it would be much less effort for them (and you and me 😅). 17 | 18 | By asking questions in a structured way (following this) it will be much easier to help you. 19 | 20 | And there's a high chance that you will find the solution along the way and you won't even have to submit it and wait for an answer. 😎 21 | 22 | As there are too many questions, I'll have to discard and close the incomplete ones. That will allow me (and others) to focus on helping people like you that follow the whole process and help us help you. 🤓 23 | - type: checkboxes 24 | id: checks 25 | attributes: 26 | label: First Check 27 | description: Please confirm and check all the following options. 28 | options: 29 | - label: I added a very descriptive title here. 30 | required: true 31 | - label: I used the GitHub search to find a similar question and didn't find it. 32 | required: true 33 | - label: I searched in the documentation/README. 34 | required: true 35 | - label: I already searched in Google "How to do X" and didn't find any information. 36 | required: true 37 | - label: I already read and followed all the tutorial in the docs/README and didn't find an answer. 38 | required: true 39 | - type: checkboxes 40 | id: help 41 | attributes: 42 | label: Commit to Help 43 | description: | 44 | After submitting this, I commit to one of: 45 | 46 | * Read open questions until I find 2 where I can help someone and add a comment to help there. 47 | * I already hit the "watch" button in this repository to receive notifications and I commit to help at least 2 people that ask questions in the future. 48 | 49 | options: 50 | - label: I commit to help with one of those options 👆 51 | required: true 52 | - type: textarea 53 | id: example 54 | attributes: 55 | label: Example Code 56 | description: | 57 | Please add a self-contained, [minimal, reproducible, example](https://stackoverflow.com/help/minimal-reproducible-example) with your use case. 58 | 59 | If I (or someone) can copy it, run it, and see it right away, there's a much higher chance I (or someone) will be able to help you. 60 | 61 | placeholder: | 62 | Write your example code here. 63 | render: Text 64 | validations: 65 | required: true 66 | - type: textarea 67 | id: description 68 | attributes: 69 | label: Description 70 | description: | 71 | What is the problem, question, or error? 72 | 73 | Write a short description telling me what you are doing, what you expect to happen, and what is currently happening. 74 | placeholder: | 75 | * Open the browser and call the endpoint `/`. 76 | * It returns a JSON with `{"message": "Hello World"}`. 77 | * But I expected it to return `{"message": "Hello Morty"}`. 78 | validations: 79 | required: true 80 | - type: dropdown 81 | id: os 82 | attributes: 83 | label: Operating System 84 | description: What operating system are you on? 85 | multiple: true 86 | options: 87 | - Linux 88 | - Windows 89 | - macOS 90 | - Other 91 | validations: 92 | required: true 93 | - type: textarea 94 | id: os-details 95 | attributes: 96 | label: Operating System Details 97 | description: You can add more details about your operating system here, in particular if you chose "Other". 98 | validations: 99 | required: true 100 | - type: input 101 | id: python-version 102 | attributes: 103 | label: Python Version 104 | description: | 105 | What Python version are you using? 106 | 107 | You can find the Python version with: 108 | 109 | ```bash 110 | python --version 111 | ``` 112 | validations: 113 | required: true 114 | - type: textarea 115 | id: context 116 | attributes: 117 | label: Additional Context 118 | description: Add any additional context information or screenshots you think are useful. 119 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## DEPRECATED 🚨 2 | 3 | This Docker image is now deprecated. There's no need to use it, you can just use Uvicorn with `--workers`. ✨ 4 | 5 | Read more about it below. 6 | 7 | --- 8 | 9 | [![Test](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/actions/workflows/test.yml/badge.svg)](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/actions/workflows/test.yml) [![Deploy](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/workflows/Deploy/badge.svg)](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/actions?query=workflow%3ADeploy) 10 | 11 | ## Supported tags and respective `Dockerfile` links 12 | 13 | * [`python3.11`, `latest` _(Dockerfile)_](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/blob/master/docker-images/python3.11.dockerfile) 14 | * [`python3.10`, _(Dockerfile)_](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/blob/master/docker-images/python3.10.dockerfile) 15 | * [`python3.11-slim` _(Dockerfile)_](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/blob/master/docker-images/python3.11-slim.dockerfile) 16 | * [`python3.10-slim` _(Dockerfile)_](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/blob/master/docker-images/python3.10-slim.dockerfile) 17 | 18 | ## Deprecated tags 19 | 20 | 🚨 These tags are no longer supported or maintained, they are removed from the GitHub repository, but the last versions pushed might still be available in Docker Hub if anyone has been pulling them: 21 | 22 | * `python3.9` 23 | * `python3.9-slim` 24 | * `python3.8` 25 | * `python3.8-slim` 26 | * `python3.7` 27 | * `python3.9-alpine3.14` 28 | * `python3.8-alpine3.10` 29 | * `python3.7-alpine3.8` 30 | * `python3.6` 31 | * `python3.6-alpine3.8` 32 | 33 | The last date tags for these versions are: 34 | 35 | * `python3.9-2025-11-09` 36 | * `python3.9-slim-2025-11-09` 37 | * `python3.8-2024-11-02` 38 | * `python3.8-slim-2024-11-02` 39 | * `python3.7-2024-11-02` 40 | * `python3.9-alpine3.14-2024-03-11` 41 | * `python3.8-alpine3.10-2024-01-29` 42 | * `python3.7-alpine3.8-2024-03-11` 43 | * `python3.6-2022-11-25` 44 | * `python3.6-alpine3.8-2022-11-25` 45 | 46 | --- 47 | 48 | **Note**: There are [tags for each build date](https://hub.docker.com/r/tiangolo/uvicorn-gunicorn-fastapi/tags). If you need to "pin" the Docker image version you use, you can select one of those tags. E.g. `tiangolo/uvicorn-gunicorn-fastapi:python3.11-2024-11-02`. 49 | 50 | # uvicorn-gunicorn-fastapi 51 | 52 | [**Docker**](https://www.docker.com/) image with [**Uvicorn**](https://www.uvicorn.org/) managed by [**Gunicorn**](https://gunicorn.org/) for high-performance [**FastAPI**](https://fastapi.tiangolo.com/) web applications in **[Python](https://www.python.org/)** with performance auto-tuning. 53 | 54 | **GitHub repo**: [https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker) 55 | 56 | **Docker Hub image**: [https://hub.docker.com/r/tiangolo/uvicorn-gunicorn-fastapi/](https://hub.docker.com/r/tiangolo/uvicorn-gunicorn-fastapi/) 57 | 58 | ## Description 59 | 60 | **FastAPI** has shown to be a Python web framework with [one of the best performances, as measured by third-party benchmarks](https://www.techempower.com/benchmarks/#section=test&runid=a979de55-980d-4721-a46f-77298b3f3923&hw=ph&test=fortune&l=zijzen-7), thanks to being based on and powered by [**Starlette**](https://www.starlette.io/). 61 | 62 | The achievable performance is on par with (and in many cases superior to) **Go** and **Node.js** frameworks. 63 | 64 | This image has an **auto-tuning** mechanism included to start a number of worker processes based on the available CPU cores. That way you can just add your code and get **high performance** automatically, which is useful in **simple deployments**. 65 | 66 | ## 🚨 WARNING: You Probably Don't Need this Docker Image 67 | 68 | You are probably using **Kubernetes** or similar tools. In that case, you probably **don't need this image** (or any other **similar base image**). You are probably better off **building a Docker image from scratch** as explained in the docs for [FastAPI in Containers - Docker: Build a Docker Image for FastAPI](https://fastapi.tiangolo.com/deployment/docker/#replication-number-of-processes). 69 | 70 | ### Cluster Replication 71 | 72 | If you have a cluster of machines with **Kubernetes**, Docker Swarm Mode, Nomad, or other similar complex system to manage distributed containers on multiple machines, then you will probably want to **handle replication** at the **cluster level** instead of using a **process manager** (like Gunicorn with Uvicorn workers) in each container, which is what this Docker image does. 73 | 74 | In those cases (e.g. using Kubernetes) you would probably want to build a **Docker image from scratch**, installing your dependencies, and running **a single Uvicorn process** instead of this image. 75 | 76 | For example, your `Dockerfile` could look like: 77 | 78 | ```Dockerfile 79 | FROM python:3.11 80 | 81 | WORKDIR /code 82 | 83 | COPY ./requirements.txt /code/requirements.txt 84 | 85 | RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt 86 | 87 | COPY ./app /code/app 88 | 89 | CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80"] 90 | ``` 91 | 92 | You can read more about this in the [FastAPI documentation about: FastAPI in Containers - Docker](https://fastapi.tiangolo.com/deployment/docker/#replication-number-of-processes). 93 | 94 | ### Multiple Workers 95 | 96 | If you definitely want to have multiple workers on a single container, Uvicorn now supports handling subprocesses, including restarting dead ones. So there's no need for Gunicorn to manage multiple workers in a single container. 97 | 98 | You could modify the example `Dockerfile` from above, adding the `--workers` option to Uvicorn, like: 99 | 100 | ```Dockerfile 101 | FROM python:3.11 102 | 103 | WORKDIR /code 104 | 105 | COPY ./requirements.txt /code/requirements.txt 106 | 107 | RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt 108 | 109 | COPY ./app /code/app 110 | 111 | CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "80", "--workers", "4"] 112 | ``` 113 | 114 | That's all you need. You don't need this Docker image at all. 😅 115 | 116 | You can read more about it in the [FastAPI Docs about Deployment with Docker](https://fastapi.tiangolo.com/deployment/docker/). 117 | 118 | ## Technical Details 119 | 120 | Uvicorn didn't have support for managing worker processing including restarting dead workers. But now it does. 121 | 122 | Before that, Gunicorn could be used as a process manager, running Uvicorn workers. This added complexity that is no longer necessary. 123 | 124 | ## Legacy Docs 125 | 126 | The rest of this document is kept for historical reasons, but you probably don't need it. 😅 127 | 128 | ### `tiangolo/uvicorn-gunicorn-fastapi` 129 | 130 | This image will set a sensible configuration based on the server it is running on (the amount of CPU cores available) without making sacrifices. 131 | 132 | It has sensible defaults, but you can configure it with environment variables or override the configuration files. 133 | 134 | There are also slim versions. If you want one of those, use one of the tags from above. 135 | 136 | ### `tiangolo/uvicorn-gunicorn` 137 | 138 | This image (`tiangolo/uvicorn-gunicorn-fastapi`) is based on [**tiangolo/uvicorn-gunicorn**](https://github.com/tiangolo/uvicorn-gunicorn-docker). 139 | 140 | That image is what actually does all the work. 141 | 142 | This image just installs FastAPI and has the documentation specifically targeted at FastAPI. 143 | 144 | If you feel confident about your knowledge of Uvicorn, Gunicorn and ASGI, you can use that image directly. 145 | 146 | ### `tiangolo/uvicorn-gunicorn-starlette` 147 | 148 | There is a sibling Docker image: [**tiangolo/uvicorn-gunicorn-starlette**](https://github.com/tiangolo/uvicorn-gunicorn-starlette-docker) 149 | 150 | If you are creating a new [**Starlette**](https://www.starlette.io/) web application and you want to discard all the additional features from FastAPI you should use [**tiangolo/uvicorn-gunicorn-starlette**](https://github.com/tiangolo/uvicorn-gunicorn-starlette-docker) instead. 151 | 152 | **Note**: FastAPI is based on Starlette and adds several features on top of it. Useful for APIs and other cases: data validation, data conversion, documentation with OpenAPI, dependency injection, security/authentication and others. 153 | 154 | ## How to use 155 | 156 | You don't need to clone the GitHub repo. 157 | 158 | You can use this image as a base image for other images. 159 | 160 | Assuming you have a file `requirements.txt`, you could have a `Dockerfile` like this: 161 | 162 | ```Dockerfile 163 | FROM tiangolo/uvicorn-gunicorn-fastapi:python3.11 164 | 165 | COPY ./requirements.txt /app/requirements.txt 166 | 167 | RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt 168 | 169 | COPY ./app /app 170 | ``` 171 | 172 | It will expect a file at `/app/app/main.py`. 173 | 174 | Or otherwise a file at `/app/main.py`. 175 | 176 | And will expect it to contain a variable `app` with your FastAPI application. 177 | 178 | Then you can build your image from the directory that has your `Dockerfile`, e.g: 179 | 180 | ```bash 181 | docker build -t myimage ./ 182 | ``` 183 | 184 | ## Quick Start 185 | 186 | ### Build your Image 187 | 188 | * Go to your project directory. 189 | * Create a `Dockerfile` with: 190 | 191 | ```Dockerfile 192 | FROM tiangolo/uvicorn-gunicorn-fastapi:python3.11 193 | 194 | COPY ./requirements.txt /app/requirements.txt 195 | 196 | RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt 197 | 198 | COPY ./app /app 199 | ``` 200 | 201 | * Create an `app` directory and enter in it. 202 | * Create a `main.py` file with: 203 | 204 | ```Python 205 | from fastapi import FastAPI 206 | 207 | app = FastAPI() 208 | 209 | 210 | @app.get("/") 211 | def read_root(): 212 | return {"Hello": "World"} 213 | 214 | 215 | @app.get("/items/{item_id}") 216 | def read_item(item_id: int, q: str = None): 217 | return {"item_id": item_id, "q": q} 218 | ``` 219 | 220 | * You should now have a directory structure like: 221 | 222 | ``` 223 | . 224 | ├── app 225 | │ └── main.py 226 | └── Dockerfile 227 | ``` 228 | 229 | * Go to the project directory (in where your `Dockerfile` is, containing your `app` directory). 230 | * Build your FastAPI image: 231 | 232 | ```bash 233 | docker build -t myimage . 234 | ``` 235 | 236 | * Run a container based on your image: 237 | 238 | ```bash 239 | docker run -d --name mycontainer -p 80:80 myimage 240 | ``` 241 | 242 | Now you have an optimized FastAPI server in a Docker container. Auto-tuned for your current server (and number of CPU cores). 243 | 244 | ### Check it 245 | 246 | You should be able to check it in your Docker container's URL, for example: http://192.168.99.100/items/5?q=somequery or http://127.0.0.1/items/5?q=somequery (or equivalent, using your Docker host). 247 | 248 | You will see something like: 249 | 250 | ```JSON 251 | {"item_id": 5, "q": "somequery"} 252 | ``` 253 | 254 | ### Interactive API docs 255 | 256 | Now you can go to http://192.168.99.100/docs or http://127.0.0.1/docs (or equivalent, using your Docker host). 257 | 258 | You will see the automatic interactive API documentation (provided by Swagger UI): 259 | 260 | ![Swagger UI](https://fastapi.tiangolo.com/img/index/index-01-swagger-ui-simple.png) 261 | 262 | ### Alternative API docs 263 | 264 | And you can also go to http://192.168.99.100/redoc or http://127.0.0.1/redoc(or equivalent, using your Docker host). 265 | 266 | You will see the alternative automatic documentation (provided by ReDoc): 267 | 268 | ![ReDoc](https://fastapi.tiangolo.com/img/index/index-02-redoc-simple.png) 269 | 270 | ## Dependencies and packages 271 | 272 | You will probably also want to add any dependencies for your app and pin them to a specific version, probably including Uvicorn, Gunicorn, and FastAPI. 273 | 274 | This way you can make sure your app always works as expected. 275 | 276 | You could install packages with `pip` commands in your `Dockerfile`, using a `requirements.txt`, or even using [Poetry](https://python-poetry.org/). 277 | 278 | And then you can upgrade those dependencies in a controlled way, running your tests, making sure that everything works, but without breaking your production application if some new version is not compatible. 279 | 280 | ### Using Poetry 281 | 282 | Here's a small example of one of the ways you could install your dependencies making sure you have a pinned version for each package. 283 | 284 | Let's say you have a project managed with [Poetry](https://python-poetry.org/), so, you have your package dependencies in a file `pyproject.toml`. And possibly a file `poetry.lock`. 285 | 286 | Then you could have a `Dockerfile` using Docker multi-stage building with: 287 | 288 | ```Dockerfile 289 | FROM python:3.11 as requirements-stage 290 | 291 | WORKDIR /tmp 292 | 293 | RUN pip install poetry 294 | 295 | COPY ./pyproject.toml ./poetry.lock* /tmp/ 296 | 297 | RUN poetry export -f requirements.txt --output requirements.txt --without-hashes 298 | 299 | FROM tiangolo/uvicorn-gunicorn-fastapi:python3.11 300 | 301 | COPY --from=requirements-stage /tmp/requirements.txt /app/requirements.txt 302 | 303 | RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt 304 | 305 | COPY ./app /app 306 | ``` 307 | 308 | That will: 309 | 310 | * Install poetry and configure it for running inside of the Docker container. 311 | * Copy your application requirements. 312 | * Because it uses `./poetry.lock*` (ending with a `*`), it won't crash if that file is not available yet. 313 | * Install the dependencies. 314 | * Then copy your app code. 315 | 316 | It's important to copy the app code *after* installing the dependencies, that way you can take advantage of Docker's cache. That way it won't have to install everything from scratch every time you update your application files, only when you add new dependencies. 317 | 318 | This also applies for any other way you use to install your dependencies. If you use a `requirements.txt`, copy it alone and install all the dependencies on the top of the `Dockerfile`, and add your app code after it. 319 | 320 | ## Advanced usage 321 | 322 | ### Environment variables 323 | 324 | These are the environment variables that you can set in the container to configure it and their default values: 325 | 326 | #### `MODULE_NAME` 327 | 328 | The Python "module" (file) to be imported by Gunicorn, this module would contain the actual application in a variable. 329 | 330 | By default: 331 | 332 | * `app.main` if there's a file `/app/app/main.py` or 333 | * `main` if there's a file `/app/main.py` 334 | 335 | For example, if your main file was at `/app/custom_app/custom_main.py`, you could set it like: 336 | 337 | ```bash 338 | docker run -d -p 80:80 -e MODULE_NAME="custom_app.custom_main" myimage 339 | ``` 340 | 341 | #### `VARIABLE_NAME` 342 | 343 | The variable inside of the Python module that contains the FastAPI application. 344 | 345 | By default: 346 | 347 | * `app` 348 | 349 | For example, if your main Python file has something like: 350 | 351 | ```Python 352 | from fastapi import FastAPI 353 | 354 | api = FastAPI() 355 | 356 | 357 | @api.get("/") 358 | def read_root(): 359 | return {"Hello": "World"} 360 | ``` 361 | 362 | In this case `api` would be the variable with the FastAPI application. You could set it like: 363 | 364 | ```bash 365 | docker run -d -p 80:80 -e VARIABLE_NAME="api" myimage 366 | ``` 367 | 368 | #### `APP_MODULE` 369 | 370 | The string with the Python module and the variable name passed to Gunicorn. 371 | 372 | By default, set based on the variables `MODULE_NAME` and `VARIABLE_NAME`: 373 | 374 | * `app.main:app` or 375 | * `main:app` 376 | 377 | You can set it like: 378 | 379 | ```bash 380 | docker run -d -p 80:80 -e APP_MODULE="custom_app.custom_main:api" myimage 381 | ``` 382 | 383 | #### `GUNICORN_CONF` 384 | 385 | The path to a Gunicorn Python configuration file. 386 | 387 | By default: 388 | 389 | * `/app/gunicorn_conf.py` if it exists 390 | * `/app/app/gunicorn_conf.py` if it exists 391 | * `/gunicorn_conf.py` (the included default) 392 | 393 | You can set it like: 394 | 395 | ```bash 396 | docker run -d -p 80:80 -e GUNICORN_CONF="/app/custom_gunicorn_conf.py" myimage 397 | ``` 398 | 399 | You can use the [config file from the base image](https://github.com/tiangolo/uvicorn-gunicorn-docker/blob/master/docker-images/gunicorn_conf.py) as a starting point for yours. 400 | 401 | #### `WORKERS_PER_CORE` 402 | 403 | This image will check how many CPU cores are available in the current server running your container. 404 | 405 | It will set the number of workers to the number of CPU cores multiplied by this value. 406 | 407 | By default: 408 | 409 | * `1` 410 | 411 | You can set it like: 412 | 413 | ```bash 414 | docker run -d -p 80:80 -e WORKERS_PER_CORE="3" myimage 415 | ``` 416 | 417 | If you used the value `3` in a server with 2 CPU cores, it would run 6 worker processes. 418 | 419 | You can use floating point values too. 420 | 421 | So, for example, if you have a big server (let's say, with 8 CPU cores) running several applications, and you have a FastAPI application that you know won't need high performance. And you don't want to waste server resources. You could make it use `0.5` workers per CPU core. For example: 422 | 423 | ```bash 424 | docker run -d -p 80:80 -e WORKERS_PER_CORE="0.5" myimage 425 | ``` 426 | 427 | In a server with 8 CPU cores, this would make it start only 4 worker processes. 428 | 429 | **Note**: By default, if `WORKERS_PER_CORE` is `1` and the server has only 1 CPU core, instead of starting 1 single worker, it will start 2. This is to avoid bad performance and blocking applications (server application) on small machines (server machine/cloud/etc). This can be overridden using `WEB_CONCURRENCY`. 430 | 431 | #### `MAX_WORKERS` 432 | 433 | Set the maximum number of workers to use. 434 | 435 | You can use it to let the image compute the number of workers automatically but making sure it's limited to a maximum. 436 | 437 | This can be useful, for example, if each worker uses a database connection and your database has a maximum limit of open connections. 438 | 439 | By default it's not set, meaning that it's unlimited. 440 | 441 | You can set it like: 442 | 443 | ```bash 444 | docker run -d -p 80:80 -e MAX_WORKERS="24" myimage 445 | ``` 446 | 447 | This would make the image start at most 24 workers, independent of how many CPU cores are available in the server. 448 | 449 | #### `WEB_CONCURRENCY` 450 | 451 | Override the automatic definition of number of workers. 452 | 453 | By default: 454 | 455 | * Set to the number of CPU cores in the current server multiplied by the environment variable `WORKERS_PER_CORE`. So, in a server with 2 cores, by default it will be set to `2`. 456 | 457 | You can set it like: 458 | 459 | ```bash 460 | docker run -d -p 80:80 -e WEB_CONCURRENCY="2" myimage 461 | ``` 462 | 463 | This would make the image start 2 worker processes, independent of how many CPU cores are available in the server. 464 | 465 | #### `HOST` 466 | 467 | The "host" used by Gunicorn, the IP where Gunicorn will listen for requests. 468 | 469 | It is the host inside of the container. 470 | 471 | So, for example, if you set this variable to `127.0.0.1`, it will only be available inside the container, not in the host running it. 472 | 473 | It's is provided for completeness, but you probably shouldn't change it. 474 | 475 | By default: 476 | 477 | * `0.0.0.0` 478 | 479 | #### `PORT` 480 | 481 | The port the container should listen on. 482 | 483 | If you are running your container in a restrictive environment that forces you to use some specific port (like `8080`) you can set it with this variable. 484 | 485 | By default: 486 | 487 | * `80` 488 | 489 | You can set it like: 490 | 491 | ```bash 492 | docker run -d -p 80:8080 -e PORT="8080" myimage 493 | ``` 494 | 495 | #### `BIND` 496 | 497 | The actual host and port passed to Gunicorn. 498 | 499 | By default, set based on the variables `HOST` and `PORT`. 500 | 501 | So, if you didn't change anything, it will be set by default to: 502 | 503 | * `0.0.0.0:80` 504 | 505 | You can set it like: 506 | 507 | ```bash 508 | docker run -d -p 80:8080 -e BIND="0.0.0.0:8080" myimage 509 | ``` 510 | 511 | #### `LOG_LEVEL` 512 | 513 | The log level for Gunicorn. 514 | 515 | One of: 516 | 517 | * `debug` 518 | * `info` 519 | * `warning` 520 | * `error` 521 | * `critical` 522 | 523 | By default, set to `info`. 524 | 525 | If you need to squeeze more performance sacrificing logging, set it to `warning`, for example: 526 | 527 | You can set it like: 528 | 529 | ```bash 530 | docker run -d -p 80:8080 -e LOG_LEVEL="warning" myimage 531 | ``` 532 | 533 | #### `WORKER_CLASS` 534 | 535 | The class to be used by Gunicorn for the workers. 536 | 537 | By default, set to `uvicorn.workers.UvicornWorker`. 538 | 539 | The fact that it uses Uvicorn is what allows using ASGI frameworks like FastAPI, and that is also what provides the maximum performance. 540 | 541 | You probably shouldn't change it. 542 | 543 | But if for some reason you need to use the alternative Uvicorn worker: `uvicorn.workers.UvicornH11Worker` you can set it with this environment variable. 544 | 545 | You can set it like: 546 | 547 | ```bash 548 | docker run -d -p 80:8080 -e WORKER_CLASS="uvicorn.workers.UvicornH11Worker" myimage 549 | ``` 550 | 551 | #### `TIMEOUT` 552 | 553 | Workers silent for more than this many seconds are killed and restarted. 554 | 555 | Read more about it in the [Gunicorn docs: timeout](https://docs.gunicorn.org/en/stable/settings.html#timeout). 556 | 557 | By default, set to `120`. 558 | 559 | Notice that Uvicorn and ASGI frameworks like FastAPI are async, not sync. So it's probably safe to have higher timeouts than for sync workers. 560 | 561 | You can set it like: 562 | 563 | ```bash 564 | docker run -d -p 80:8080 -e TIMEOUT="20" myimage 565 | ``` 566 | 567 | #### `KEEP_ALIVE` 568 | 569 | The number of seconds to wait for requests on a Keep-Alive connection. 570 | 571 | Read more about it in the [Gunicorn docs: keepalive](https://docs.gunicorn.org/en/stable/settings.html#keepalive). 572 | 573 | By default, set to `2`. 574 | 575 | You can set it like: 576 | 577 | ```bash 578 | docker run -d -p 80:8080 -e KEEP_ALIVE="20" myimage 579 | ``` 580 | 581 | #### `GRACEFUL_TIMEOUT` 582 | 583 | Timeout for graceful workers restart. 584 | 585 | Read more about it in the [Gunicorn docs: graceful-timeout](https://docs.gunicorn.org/en/stable/settings.html#graceful-timeout). 586 | 587 | By default, set to `120`. 588 | 589 | You can set it like: 590 | 591 | ```bash 592 | docker run -d -p 80:8080 -e GRACEFUL_TIMEOUT="20" myimage 593 | ``` 594 | 595 | #### `ACCESS_LOG` 596 | 597 | The access log file to write to. 598 | 599 | By default `"-"`, which means stdout (print in the Docker logs). 600 | 601 | If you want to disable `ACCESS_LOG`, set it to an empty value. 602 | 603 | For example, you could disable it with: 604 | 605 | ```bash 606 | docker run -d -p 80:8080 -e ACCESS_LOG= myimage 607 | ``` 608 | 609 | #### `ERROR_LOG` 610 | 611 | The error log file to write to. 612 | 613 | By default `"-"`, which means stderr (print in the Docker logs). 614 | 615 | If you want to disable `ERROR_LOG`, set it to an empty value. 616 | 617 | For example, you could disable it with: 618 | 619 | ```bash 620 | docker run -d -p 80:8080 -e ERROR_LOG= myimage 621 | ``` 622 | 623 | #### `GUNICORN_CMD_ARGS` 624 | 625 | Any additional command line settings for Gunicorn can be passed in the `GUNICORN_CMD_ARGS` environment variable. 626 | 627 | Read more about it in the [Gunicorn docs: Settings](https://docs.gunicorn.org/en/stable/settings.html#settings). 628 | 629 | These settings will have precedence over the other environment variables and any Gunicorn config file. 630 | 631 | For example, if you have a custom TLS/SSL certificate that you want to use, you could copy them to the Docker image or mount them in the container, and set [`--keyfile` and `--certfile`](http://docs.gunicorn.org/en/latest/settings.html#ssl) to the location of the files, for example: 632 | 633 | ```bash 634 | docker run -d -p 80:8080 -e GUNICORN_CMD_ARGS="--keyfile=/secrets/key.pem --certfile=/secrets/cert.pem" -e PORT=443 myimage 635 | ``` 636 | 637 | **Note**: instead of handling TLS/SSL yourself and configuring it in the container, it's recommended to use a "TLS Termination Proxy" like [Traefik](https://docs.traefik.io/). You can read more about it in the [FastAPI documentation about HTTPS](https://fastapi.tiangolo.com/deployment/#https). 638 | 639 | #### `PRE_START_PATH` 640 | 641 | The path where to find the pre-start script. 642 | 643 | By default, set to `/app/prestart.sh`. 644 | 645 | You can set it like: 646 | 647 | ```bash 648 | docker run -d -p 80:8080 -e PRE_START_PATH="/custom/script.sh" myimage 649 | ``` 650 | 651 | ### Custom Gunicorn configuration file 652 | 653 | The image includes a default Gunicorn Python config file at `/gunicorn_conf.py`. 654 | 655 | It uses the environment variables declared above to set all the configurations. 656 | 657 | You can override it by including a file in: 658 | 659 | * `/app/gunicorn_conf.py` 660 | * `/app/app/gunicorn_conf.py` 661 | * `/gunicorn_conf.py` 662 | 663 | ### Custom `/app/prestart.sh` 664 | 665 | If you need to run anything before starting the app, you can add a file `prestart.sh` to the directory `/app`. The image will automatically detect and run it before starting everything. 666 | 667 | For example, if you want to add Alembic SQL migrations (with SQLALchemy), you could create a `./app/prestart.sh` file in your code directory (that will be copied by your `Dockerfile`) with: 668 | 669 | ```bash 670 | #! /usr/bin/env bash 671 | 672 | # Let the DB start 673 | sleep 10; 674 | # Run migrations 675 | alembic upgrade head 676 | ``` 677 | 678 | and it would wait 10 seconds to give the database some time to start and then run that `alembic` command. 679 | 680 | If you need to run a Python script before starting the app, you could make the `/app/prestart.sh` file run your Python script, with something like: 681 | 682 | ```bash 683 | #! /usr/bin/env bash 684 | 685 | # Run custom Python script before starting 686 | python /app/my_custom_prestart_script.py 687 | ``` 688 | 689 | You can customize the location of the prestart script with the environment variable `PRE_START_PATH` described above. 690 | 691 | ### Development live reload 692 | 693 | The default program that is run is at `/start.sh`. It does everything described above. 694 | 695 | There's also a version for development with live auto-reload at: 696 | 697 | ```bash 698 | /start-reload.sh 699 | ``` 700 | 701 | #### Details 702 | 703 | For development, it's useful to be able to mount the contents of the application code inside of the container as a Docker "host volume", to be able to change the code and test it live, without having to build the image every time. 704 | 705 | In that case, it's also useful to run the server with live auto-reload, so that it re-starts automatically at every code change. 706 | 707 | The additional script `/start-reload.sh` runs Uvicorn alone (without Gunicorn) and in a single process. 708 | 709 | It is ideal for development. 710 | 711 | #### Usage 712 | 713 | For example, instead of running: 714 | 715 | ```bash 716 | docker run -d -p 80:80 myimage 717 | ``` 718 | 719 | You could run: 720 | 721 | ```bash 722 | docker run -d -p 80:80 -v $(pwd):/app myimage /start-reload.sh 723 | ``` 724 | 725 | * `-v $(pwd):/app`: means that the directory `$(pwd)` should be mounted as a volume inside of the container at `/app`. 726 | * `$(pwd)`: runs `pwd` ("print working directory") and puts it as part of the string. 727 | * `/start-reload.sh`: adding something (like `/start-reload.sh`) at the end of the command, replaces the default "command" with this one. In this case, it replaces the default (`/start.sh`) with the development alternative `/start-reload.sh`. 728 | 729 | #### Development live reload - Technical Details 730 | 731 | As `/start-reload.sh` doesn't run with Gunicorn, any of the configurations you put in a `gunicorn_conf.py` file won't apply. 732 | 733 | But these environment variables will work the same as described above: 734 | 735 | * `MODULE_NAME` 736 | * `VARIABLE_NAME` 737 | * `APP_MODULE` 738 | * `HOST` 739 | * `PORT` 740 | * `LOG_LEVEL` 741 | 742 | ## 🚨 Alpine Python Warning 743 | 744 | In short: You probably shouldn't use Alpine for Python projects, instead use the `slim` Docker image versions. 745 | 746 | --- 747 | 748 | Do you want more details? Continue reading 👇 749 | 750 | Alpine is more useful for other languages where you build a static binary in one Docker image stage (using multi-stage Docker building) and then copy it to a simple Alpine image, and then just execute that binary. For example, using Go. 751 | 752 | But for Python, as Alpine doesn't use the standard tooling used for building Python extensions, when installing packages, in many cases Python (`pip`) won't find a precompiled installable package (a "wheel") for Alpine. And after debugging lots of strange errors you will realize that you have to install a lot of extra tooling and build a lot of dependencies just to use some of these common Python packages. 😩 753 | 754 | This means that, although the original Alpine image might have been small, you end up with a an image with a size comparable to the size you would have gotten if you had just used a standard Python image (based on Debian), or in some cases even larger. 🤯 755 | 756 | And in all those cases, it will take much longer to build, consuming much more resources, building dependencies for longer, and also increasing its carbon footprint, as you are using more CPU time and energy for each build. 🌳 757 | 758 | If you want slim Python images, you should instead try and use the `slim` versions that are still based on Debian, but are smaller. 🤓 759 | 760 | ## Tests 761 | 762 | All the image tags, configurations, environment variables and application options are tested. 763 | 764 | ## Release Notes 765 | 766 | ### Latest Changes 767 | 768 | #### Upgrades 769 | 770 | * ⬆ Bump uvicorn[standard] from 0.35.0 to 0.38.0. PR [#390](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/390) by [@dependabot[bot]](https://github.com/apps/dependabot). 771 | * ⬆ Bump fastapi[all] from 0.116.0 to 0.121.0. PR [#397](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/397) by [@dependabot[bot]](https://github.com/apps/dependabot). 772 | * ⬆ Bump fastapi[all] from 0.115.12 to 0.116.0. PR [#376](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/376) by [@dependabot[bot]](https://github.com/apps/dependabot). 773 | * ⬆ Bump uvicorn[standard] from 0.34.2 to 0.35.0. PR [#375](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/375) by [@dependabot[bot]](https://github.com/apps/dependabot). 774 | * ⬆ Bump fastapi[all] from 0.115.9 to 0.115.12. PR [#368](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/368) by [@dependabot[bot]](https://github.com/apps/dependabot). 775 | * ⬆ Bump uvicorn[standard] from 0.34.0 to 0.34.2. PR [#370](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/370) by [@dependabot[bot]](https://github.com/apps/dependabot). 776 | * ⬆ Bump fastapi[all] from 0.115.4 to 0.115.9. PR [#366](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/366) by [@dependabot[bot]](https://github.com/apps/dependabot). 777 | * ⬆ Bump uvicorn[standard] from 0.32.0 to 0.34.0. PR [#361](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/361) by [@dependabot[bot]](https://github.com/apps/dependabot). 778 | * ⬆ Bump fastapi[all] from 0.88.0 to 0.115.4. PR [#354](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/354) by [@dependabot[bot]](https://github.com/apps/dependabot). 779 | * ⬆ Bump uvicorn[standard] from 0.20.0 to 0.32.0. PR [#352](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/352) by [@dependabot[bot]](https://github.com/apps/dependabot). 780 | * 🔥 Drop support for Python 3.7 and 3.8. PR [#355](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/355) by [@tiangolo](https://github.com/tiangolo). 781 | * ⬆ Bump gunicorn from 22.0.0 to 23.0.0. PR [#300](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/300) by [@dependabot[bot]](https://github.com/apps/dependabot). 782 | * ⬆ Bump gunicorn from 21.2.0 to 22.0.0. PR [#287](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/287) by [@dependabot[bot]](https://github.com/apps/dependabot). 783 | 784 | #### Docs 785 | 786 | * 📝 Deprecate this Docker image, use Uvicorn with `--workers` ✨. PR [#303](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/303) by [@tiangolo](https://github.com/tiangolo). 787 | * 📝 Add security policy. PR [#283](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/283) by [@tiangolo](https://github.com/tiangolo). 788 | 789 | #### Internal 790 | 791 | * 👷 Upgrade actions/checkout from v5 to v6. PR [#406](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/406) by [@tiangolo](https://github.com/tiangolo). 792 | * 👷 Upgrade `latest-changes` GitHub Action and pin `actions/checkout@v5`. PR [#404](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/404) by [@tiangolo](https://github.com/tiangolo). 793 | * 🔥 Drop support for Python 3.9. PR [#398](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/398) by [@tiangolo](https://github.com/tiangolo). 794 | * ⬆ Bump tiangolo/issue-manager from 0.5.1 to 0.6.0. PR [#387](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/387) by [@dependabot[bot]](https://github.com/apps/dependabot). 795 | * ⬆ Bump actions/setup-python from 5 to 6. PR [#380](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/380) by [@dependabot[bot]](https://github.com/apps/dependabot). 796 | * ⬆ Bump actions/labeler from 5 to 6. PR [#381](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/381) by [@dependabot[bot]](https://github.com/apps/dependabot). 797 | * ⬆ Bump actions/checkout from 4 to 5. PR [#379](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/379) by [@dependabot[bot]](https://github.com/apps/dependabot). 798 | * ⬆ Bump tiangolo/latest-changes from 0.3.2 to 0.4.0. PR [#378](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/378) by [@dependabot[bot]](https://github.com/apps/dependabot). 799 | * ⬆ Bump tiangolo/latest-changes from 0.3.1 to 0.3.2. PR [#357](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/357) by [@dependabot[bot]](https://github.com/apps/dependabot). 800 | * 👷 Update labeler config. PR [#365](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/365) by [@tiangolo](https://github.com/tiangolo). 801 | * 👷 Add CI Labeler. PR [#364](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/364) by [@tiangolo](https://github.com/tiangolo). 802 | * 🔥 Remove old unused files. PR [#356](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/356) by [@tiangolo](https://github.com/tiangolo). 803 | * ⬆ Bump tiangolo/issue-manager from 0.5.0 to 0.5.1. PR [#344](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/344) by [@dependabot[bot]](https://github.com/apps/dependabot). 804 | * 👷 Update `issue-manager.yml`. PR [#343](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/343) by [@tiangolo](https://github.com/tiangolo). 805 | * 👷 Update `latest-changes` GitHub Action. PR [#340](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/340) by [@tiangolo](https://github.com/tiangolo). 806 | * ⬆ Bump docker/build-push-action from 5 to 6. PR [#293](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/293) by [@dependabot[bot]](https://github.com/apps/dependabot). 807 | * 📌 Unpin testing dependencies. PR [#304](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/304) by [@tiangolo](https://github.com/tiangolo). 808 | * ⬆ Bump docker/login-action from 1 to 3. PR [#280](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/280) by [@dependabot[bot]](https://github.com/apps/dependabot). 809 | * ⬆ Bump docker/setup-buildx-action from 1 to 3. PR [#279](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/279) by [@dependabot[bot]](https://github.com/apps/dependabot). 810 | * ⬆ Bump docker/build-push-action from 2 to 5. PR [#278](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/278) by [@dependabot[bot]](https://github.com/apps/dependabot). 811 | * ⬆ Bump actions/setup-python from 4 to 5. PR [#277](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/277) by [@dependabot[bot]](https://github.com/apps/dependabot). 812 | * ⬆ Update black requirement from ^22.10 to ^23.3. PR [#268](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/268) by [@dependabot[bot]](https://github.com/apps/dependabot). 813 | * 🔧 Add GitHub templates for discussions and templates. PR [#281](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/281) by [@tiangolo](https://github.com/tiangolo). 814 | * 🔧 Update `latest-changes.yml`. PR [#276](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/276) by [@alejsdev](https://github.com/alejsdev). 815 | 816 | ### 0.8.0 817 | 818 | #### Features 819 | 820 | * ✨ Add support for multi-arch builds, including support for arm64 (e.g. Mac M1). PR [#273](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/273) by [@tiangolo](https://github.com/tiangolo). 821 | 822 | #### Docs 823 | 824 | * 📝 Update test badge in `README.md`. PR [#275](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/275) by [@alejsdev](https://github.com/alejsdev). 825 | * 📝 Update test badge in `README.md`. PR [#274](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/274) by [@alejsdev](https://github.com/alejsdev). 826 | 827 | #### Upgrades 828 | 829 | * ⬆ Bump gunicorn from 20.1.0 to 21.2.0. PR [#270](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/270) by [@dependabot[bot]](https://github.com/apps/dependabot). 830 | * ⬆️ Bump fastapi[all] from 0.87.0 to 0.88.0. PR [#222](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/222) by [@dependabot[bot]](https://github.com/apps/dependabot). 831 | 832 | #### Internal 833 | 834 | * ⬆ Update mypy requirement from ^0.991 to ^1.4. PR [#269](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/269) by [@dependabot[bot]](https://github.com/apps/dependabot). 835 | * ⬆ Bump actions/checkout from 3 to 4. PR [#266](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/266) by [@dependabot[bot]](https://github.com/apps/dependabot). 836 | * ⬆ Bump peter-evans/dockerhub-description from 3 to 4. PR [#267](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/267) by [@dependabot[bot]](https://github.com/apps/dependabot). 837 | * ⬆ Bump actions/setup-python from 4.3.0 to 5.0.0. PR [#265](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/265) by [@dependabot[bot]](https://github.com/apps/dependabot). 838 | * ⬆ Bump tiangolo/issue-manager from 0.4.0 to 0.5.0. PR [#264](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/264) by [@dependabot[bot]](https://github.com/apps/dependabot). 839 | * 👷 Update dependabot. PR [#253](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/253) by [@tiangolo](https://github.com/tiangolo). 840 | * 👷 Update token for latest changes. PR [#247](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/247) by [@tiangolo](https://github.com/tiangolo). 841 | * 👷 Add GitHub Action for Docker Hub description. PR [#221](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/221) by [@tiangolo](https://github.com/tiangolo). 842 | * ⬆️ Update mypy requirement from ^0.971 to ^0.991. PR [#214](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/214) by [@dependabot[bot]](https://github.com/apps/dependabot). 843 | * ⬆️ Update autoflake requirement from ^1.3.1 to ^2.0.0. PR [#215](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/215) by [@dependabot[bot]](https://github.com/apps/dependabot). 844 | 845 | ### 0.7.0 846 | 847 | Highlights of this release: 848 | 849 | * Support for Python 3.10 and 3.11. 850 | * Deprecation of Python 3.6. 851 | * The last Python 3.6 image tag was pushed and is available in Docker Hub, but it won't be updated or maintained anymore. 852 | * The last image with a date tag is `python3.6-2022-11-25`. 853 | * Upgraded versions of all the dependencies. 854 | 855 | #### Features 856 | 857 | * ✨ Add support for Python 3.10 and 3.11. PR [#220](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/220) by [@tiangolo](https://github.com/tiangolo). 858 | * ✨ Add Python 3.9 and Python 3.9 Alpine. PR [#67](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/67) by [@graue70](https://github.com/graue70). 859 | 860 | #### Breaking Changes 861 | 862 | * 🔥 Deprecate and remove Python 3.6. PR [#211](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/211) by [@tiangolo](https://github.com/tiangolo). 863 | 864 | #### Upgrades 865 | 866 | * ⬆️ Upgrade FastAPI and Uvicorn versions. PR [#212](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/212) by [@tiangolo](https://github.com/tiangolo). 867 | * ⬆️ Upgrade packages to the last version that supports Python 3.6. PR [#207](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/207) by [@tiangolo](https://github.com/tiangolo). 868 | 869 | #### Docs 870 | 871 | * 📝 Add note to discourage Alpine with Python. PR [#122](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/122) by [@tiangolo](https://github.com/tiangolo). 872 | * 📝 Add warning for Kubernetes, when to use this image. PR [#121](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/121) by [@tiangolo](https://github.com/tiangolo). 873 | * ✏ Fix typo, repeated word on README. PR [#96](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/96) by [@shelbylsmith](https://github.com/shelbylsmith). 874 | 875 | #### Internal 876 | 877 | * ⬆️ Update black requirement from ^20.8b1 to ^22.10. PR [#216](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/216) by [@dependabot[bot]](https://github.com/apps/dependabot). 878 | * ⬆️ Update docker requirement from ^5.0.3 to ^6.0.1. PR [#217](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/217) by [@dependabot[bot]](https://github.com/apps/dependabot). 879 | * 🔥 Remove old Travis file. PR [#219](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/219) by [@tiangolo](https://github.com/tiangolo). 880 | * ⬆️ Upgrade CI OS. PR [#218](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/218) by [@tiangolo](https://github.com/tiangolo). 881 | * 🔧 Update Dependabot config. PR [#213](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/213) by [@tiangolo](https://github.com/tiangolo). 882 | * 👷 Add scheduled CI. PR [#210](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/210) by [@tiangolo](https://github.com/tiangolo). 883 | * 👷 Add alls-green GitHub Action. PR [#209](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/209) by [@tiangolo](https://github.com/tiangolo). 884 | * 👷 Do not run double CI, run on push only on master. PR [#208](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/208) by [@tiangolo](https://github.com/tiangolo). 885 | * ⬆️ Bump actions/setup-python from 4.1.0 to 4.3.0. PR [#201](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/201) by [@dependabot[bot]](https://github.com/apps/dependabot). 886 | * ⬆️ Update black requirement from ^19.10b0 to ^20.8b1. PR [#113](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/113) by [@dependabot[bot]](https://github.com/apps/dependabot). 887 | * ⬆️ Update docker requirement from ^4.2.0 to ^5.0.3. PR [#125](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/125) by [@dependabot[bot]](https://github.com/apps/dependabot). 888 | * ⬆️ Bump actions/checkout from 2 to 3.1.0. PR [#194](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/194) by [@dependabot[bot]](https://github.com/apps/dependabot). 889 | * ⬆️ Update mypy requirement from ^0.770 to ^0.971. PR [#184](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/184) by [@dependabot[bot]](https://github.com/apps/dependabot). 890 | * ⬆️ Update isort requirement from ^4.3.21 to ^5.8.0. PR [#116](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/116) by [@dependabot[bot]](https://github.com/apps/dependabot). 891 | * ⬆️ Bump tiangolo/issue-manager from 0.2.0 to 0.4.0. PR [#110](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/110) by [@dependabot[bot]](https://github.com/apps/dependabot). 892 | * ⬆️ Bump actions/setup-python from 1 to 4.1.0. PR [#182](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/182) by [@dependabot[bot]](https://github.com/apps/dependabot). 893 | * ⬆️ Update pytest requirement from ^5.4.1 to ^7.0.1. PR [#153](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/153) by [@dependabot[bot]](https://github.com/apps/dependabot). 894 | * 📌 Add external dependencies and Dependabot to get automatic upgrade PRs. PR [#109](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/109) by [@tiangolo](https://github.com/tiangolo). 895 | * 👷 Update Latest Changes. PR [#108](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/108) by [@tiangolo](https://github.com/tiangolo). 896 | * 👷 Allow GitHub workflow dispatch to trigger test and deploy. PR [#93](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/93) by [@tiangolo](https://github.com/tiangolo). 897 | * 👷 Add latest-changes GitHub action, update issue-manager, add funding. PR [#70](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/70) by [@tiangolo](https://github.com/tiangolo). 898 | 899 | ### 0.6.0 900 | 901 | * Add docs about installing and pinning dependencies. PR [#41](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/41). 902 | * Add `slim` version. PR [#40](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/40). 903 | * Update and refactor bringing all the new features from the base image. Includes: 904 | * Centralize, simplify, and deduplicate code and setup 905 | * Move CI to GitHub actions 906 | * Add Python 3.8 (and Alpine) 907 | * Add new configs and docs: 908 | * `WORKER_CLASS` 909 | * `TIMEOUT` 910 | * `KEEP_ALIVE` 911 | * `GRACEFUL_TIMEOUT` 912 | * `ACCESS_LOG` 913 | * `ERROR_LOG` 914 | * `GUNICORN_CMD_ARGS` 915 | * `MAX_WORKERS` 916 | * PR [#39](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/39). 917 | * Disable pip cache during installation. PR [#38](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/38). 918 | * Migrate local development from Pipenv to Poetry. PR [#34](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/34). 919 | * Add docs for custom `PRE_START_PATH` env var. PR [#33](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/33). 920 | 921 | ### 0.5.0 922 | 923 | * Refactor tests to use env vars and add image tags for each build date, like `tiangolo/uvicorn-gunicorn-fastapi:python3.7-2019-10-15`. PR [#17](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/17). 924 | * Upgrade Travis. PR [#9](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker/pull/9). 925 | 926 | ### 0.4.0 927 | 928 | * Add support for live auto-reload with an additional custom script `/start-reload.sh`, check the [updated documentation](https://github.com/tiangolo/uvicorn-gunicorn-fastapi-docker#development-live-reload). PR #6 in parent image. 929 | 930 | ### 0.3.0 931 | 932 | * Set `WORKERS_PER_CORE` by default to `1`, as it shows to have the best performance on benchmarks. 933 | * Make the default web concurrency, when `WEB_CONCURRENCY` is not set, to a minimum of 2 workers. This is to avoid bad performance and blocking applications (server application) on small machines (server machine/cloud/etc). This can be overridden using `WEB_CONCURRENCY`. This applies for example in the case where `WORKERS_PER_CORE` is set to `1` (the default) and the server has only 1 CPU core. PR #6 and PR #5 in parent image. 934 | 935 | ### 0.2.0 936 | 937 | * Make `/start.sh` run independently, reading and generating used default environment variables. And remove `/entrypoint.sh` as it doesn't modify anything in the system, only reads environment variables. PR #4 in parent image. 938 | 939 | ### 0.1.0 940 | 941 | * Add support for `/app/prestart.sh`. 942 | 943 | ## License 944 | 945 | This project is licensed under the terms of the MIT license. 946 | --------------------------------------------------------------------------------