DriversRecommendedOutdated drivers can make a good PC feel brokenScan driver issues before chasing fixes manually.Scan NowHalloween DealsAmazon USHalloween week deals: useful picks before month-endAmazon US: current deals, useful picks and tech finds.Check DealsSlow PC?RecommendedPC slow today? Run a repair scan before it gets worseResolve common Windows issues and optimize system performance.Scan Now×
Skip to content
VGSources
Blog

How to Containerize a Python Web App with Docker

Build and run a Python web app in Docker, then harden it for production with multi-stage images, non-root execution, runtime secrets, health checks and Compose.
Length10 min Posted Quest giverVGSources Team
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use a Docker image to package your Python web app, its pinned dependencies, configuration interface and runtime into one reproducible unit. A practical path is to copy the dependency file first, install it in a trusted Python base image, copy the source, bind the server to 0.0.0.0, and publish the container port to your host. For production, build dependencies in a separate stage, run as a non-root user, inject secrets at runtime and expose a health check.

The walkthrough below uses a small FastAPI application and port 8000. Replace the module name, framework command and port with the values used by your project.

As an Amazon Associate I earn from qualifying purchases.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What containerizing a Python app actually does

A container image combines your application code with its Python runtime, installed packages and the configuration interface the process expects. Starting that image creates an isolated process with the same filesystem and dependency set on a developer laptop, a CI runner or a production host. The host still supplies the kernel and hardware, but the application no longer depends on an accidentally different virtual environment.

Docker does not automatically make an application production-ready. The Dockerfile determines what is inside the image; the run command or Compose file supplies ports, environment variables, volumes and resource limits. A good design keeps build tools and secrets out of the runtime image and makes the process observable and stoppable.

Prepare the project before writing the Dockerfile

Keep the build inputs together

Place the application source, a pinned dependency definition and the Docker assets in one project directory. You can use requirements.txt, pyproject.toml with a lock file, or an equivalent package-manager format. Pinning versions makes a rebuild resolve the same dependency set instead of silently pulling a newer release.

my-python-app/
├── app.py
├── requirements.txt
├── Dockerfile
├── compose.yaml
└── .dockerignore

A minimal FastAPI example might contain this application and dependency file:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
# app.py
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def root():
    return {"status": "ok"}

@app.get("/health")
def health():
    return {"healthy": True}
# requirements.txt
fastapi==0.115.0
uvicorn[standard]==0.30.6

The exact versions are illustrative. Use versions that your application supports and commit the resulting lock or requirements file.

Exclude files that should never enter the build context

Docker sends the build context (the directory represented by the final dot in docker build ... .) to the builder. A .dockerignore prevents local environments, caches, version-control metadata, test output and secrets from being sent or copied.

.git
.gitignore
.venv/
venv/
__pycache__/
*.py[cod]
.pytest_cache/
.mypy_cache/
.coverage
.env
.env.*
tests/
dist/
build/

Do not rely on .dockerignore as a secret-management system. If a secret was copied into an image layer, removing it in a later layer does not erase it from the image history. Keep credentials outside the image and provide them when the container starts.

Build a first image for local development

Choose and record a base image

Start from a trusted Python image appropriate for the application and record its tag. A slim variant usually contains fewer operating-system packages than a full image, but packages with native extensions may require additional build tools. Keep the tag explicit so two developers do not unknowingly build against different Python releases.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Copy dependencies before source code

Docker reuses completed layers. Dependency files change less often than application code, so copy and install them before copying the rest of the source. A source edit can then reuse the expensive package-install layer. BuildKit can also cache package downloads between builds.

# syntax=docker/dockerfile:1
FROM python:3.12-slim

ENV PYTHONDONTWRITEBYTECODE=1 
    PYTHONUNBUFFERED=1

WORKDIR /app

COPY requirements.txt .
RUN --mount=type=cache,target=/root/.cache/pip 
    python -m pip install --upgrade pip 
    && python -m pip install -r requirements.txt

COPY . .

EXPOSE 8000
CMD ["python", "-m", "uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]

WORKDIR makes subsequent paths relative to /app. EXPOSE documents the port; it does not publish that port by itself. The JSON (exec) form of CMD starts Uvicorn directly, allowing Docker to deliver stop and terminate signals to the server process.

Build and run it

  1. From the directory containing the Dockerfile, build an image:
docker build -t my-python-app .
  1. Start a disposable container and map host port 8000 to container port 8000:
docker run --rm -p 8000:8000 my-python-app
  1. Open http://localhost:8000 or request the health endpoint:
curl http://localhost:8000/health

The mapping syntax is host-port:container-port. The process must listen on the container interface, commonly 0.0.0.0; listening only on 127.0.0.1 inside the container makes it unreachable through the published port.

Adapt the runtime command to your framework

FastAPI and other ASGI applications

Uvicorn is a direct ASGI server option. The module path in app:app means “import the app object from app.py.” If your file is src/main.py and the object is named api, use a matching import path such as src.main:api and ensure the package is copied into the image.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
CMD ["python", "-m", "uvicorn", "src.main:api", "--host", "0.0.0.0", "--port", "8000"]

Development reload is useful when a source directory is mounted into the container, but it watches files and is not a production process model. Keep reload tooling in a development target rather than the minimal runtime image.

Flask and other WSGI applications

A WSGI application can be served by Gunicorn. If app.py defines a Flask object named app, an exec-form command is:

CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:8000"]

Gunicorn’s worker count should be sized against the CPU and memory limits assigned to the container. More workers are not automatically faster: each worker consumes memory, and an oversize value can cause contention or restarts. Select the command that matches the framework and the deployment architecture instead of placing both Uvicorn and Gunicorn in the same image command.

Use a multi-stage Dockerfile for production

A single-stage build can leave compilers, downloaded archives and packaging tools in the final image. A multi-stage build creates wheels in a builder stage and copies only those wheels into a clean runtime stage. This separates build concerns, reduces the runtime image and removes unnecessary attack surface. The Python version below is an example; align it with your application’s support policy.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
# syntax=docker/dockerfile:1
FROM python:3.12-slim AS builder
WORKDIR /build
COPY requirements.txt .
RUN python -m pip install --upgrade pip 
    && pip wheel --no-cache-dir --wheel-dir /wheels -r requirements.txt

FROM python:3.12-slim AS runtime
WORKDIR /app

COPY --from=builder /wheels /wheels
RUN python -m pip install --no-cache-dir /wheels/* 
    && rm -rf /wheels

COPY . .
EXPOSE 8000
CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:8000"]

The builder’s pip wheel command turns the pinned requirements into installable wheel files. The runtime stage receives those files, installs them without retaining pip’s download cache, then deletes the temporary wheel directory. If a dependency needs a system library at runtime, install that library in the runtime stage; build-only compilers belong in the builder stage.

Run the production process as a non-root user

Processes in a container should not need root privileges. Create an application user, copy files with the correct ownership and switch users before the command runs:

FROM python:3.12-slim AS runtime
WORKDIR /app

RUN useradd --create-home --shell /usr/sbin/nologin appuser
COPY --from=builder /wheels /wheels
RUN python -m pip install --no-cache-dir /wheels/* 
    && rm -rf /wheels
COPY --chown=appuser:appuser . .

USER appuser
EXPOSE 8000
CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:8000"]

Check that the application writes only to directories the user can access. If it needs uploads, logs or a cache, mount a volume or provide a writable path deliberately rather than making the whole filesystem writable.

Configure secrets, ports and health checks at runtime

Keep configuration outside the image

Use environment variables or your platform’s secret manager for database URLs, signing keys and API credentials. Do not put values in ENV instructions, source files or committed Compose files. A local run can receive a variable explicitly:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
docker run --rm 
  -p 8000:8000 
  -e DATABASE_URL="$DATABASE_URL" 
  -e APP_ENV=production 
  my-python-app

Keep non-sensitive defaults in application configuration, but make production values explicit in the deployment environment. Gunicorn settings such as bind address and worker count can likewise be driven by environment-aware startup configuration when the platform needs different limits.

Add an application endpoint and a container health check

A health endpoint should answer quickly and avoid performing an expensive full business transaction. The /health route above is suitable for a basic process check. Add a Docker health check when your platform uses Docker’s health status for restart or routing decisions. The following form uses Python from the image and therefore does not require a separate curl binary:

HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 
  CMD ["python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3)"]

Place the instruction after the application and its dependencies are installed. If your health check must verify a database or another dependency, define that contract carefully; failing a liveness check because a temporary downstream service is unavailable can cause unnecessary restarts.

Rank #4
Natmisz:Dive Log Book Scuba Diving, Scuba Log Book, Scuba Diving Equipment Accessories, Gifts For Scuba Divers Men, Leather Logbook Binder, A6 Size Binders 50 Pages
  • This dive logbook is made from genuine cowhide leather, and just like fingerprints, each piece is one-of-a-kind. Each logbook flaunts its own unique personality with varying colors, textures, and markings. Over time, the leather develops its own charming "battle scars," making it even more beautiful and uniquely yours. It's like your favorite pair of jeans – the more you use it, the cooler it gets
  • Its compact A6 size (5.1in x 7.5in) makes it super easy to carry around, letting you jot down your dive logs anytime, anywhere. Whether you toss it in your dive bag or take it on the go, it’s a breeze to bring along. Perfectly suited for A6 dive logs and A6 binders. Think of it as your trusty sidekick – always ready for the next adventure
  • This dive logbook isn't just a pretty face – it's got some serious chops too. The genuine leather cover laughs in the face of splashes, and the 120g high-quality paper ensures your notes won't dissolve into a soggy mess. Perfect for those damp and drizzly dives
  • 3 Card Slots: Perfect for stashing your dive cards, transit passes, and other important cards. 1 Zipper Pocket: Keep your valuables secure and safe. 4 Receipt Pockets: Perfect for holding tickets, passports, and other essential documents We’ve included 2 removable, expandable PVC pages: Transparent PVC Page: Holds up to 3 cards, making them easy to see and use. PVC Storage Bag: Specially designed waterproof bag for those items that just hate getting wet.
  • Our dive logbook includes all the essential fields for divers to record every dive, such as date, dive site, conditions, equipment, dive stats, and dive verification, among others. With 50 pages designed to capture the key details of each dive adventure, it's perfect for PADI divers and fits the A6 size dive log requirements
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Use Compose for a repeatable local stack

Compose describes services, ports, environment variables, named volumes and resource limits in one file. It is useful when the web process needs a database or another local dependency. Keep live-reload shells and debugging tools in a development stage, while production deployments use the minimal runtime stage.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
services:
  web:
    build:
      context: .
      target: builder
    command: python -m uvicorn app:app --host 0.0.0.0 --port 8000 --reload
    ports:
      - "8000:8000"
    environment:
      APP_ENV: development
      DATABASE_URL: postgresql://app:app@db:5432/app
    volumes:
      - .:/app
    depends_on:
      - db
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 512M

  db:
    image: postgres:16
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: app
      POSTGRES_PASSWORD: change-me-for-local-use
    volumes:
      - db-data:/var/lib/postgresql/data

volumes:
  db-data:

The database image and credentials in this file are local-development examples. Use your organization’s approved database image and a secret mechanism for shared or production environments. A named volume preserves database data when the database container is recreated. The bind mount on web makes source edits visible for reload; omit it for a production-style immutable container.

Build and start the stack with:

docker compose up --build

Stop the services while retaining the named volume with docker compose down. Remove the volume only when you intentionally want to delete the local database data.

Choose between common approaches

Decision Option Use it when Trade-off
Image construction Single stage Quick experiments or a simple development image Build tools and package artifacts can remain in the runtime image
Image construction Multi-stage Production images and applications with compiled dependencies More Dockerfile structure, but a smaller and cleaner runtime
ASGI runtime Uvicorn directly FastAPI or another ASGI app where the platform supplies process management Worker management is outside the command unless you add it
WSGI/runtime management Gunicorn Flask or another WSGI app, or deployments needing managed workers Worker count must be tuned to assigned CPU and memory
Local orchestration docker run One container and a simple port mapping Ports, variables and supporting services are specified manually
Local orchestration Compose Web app plus database, volumes, environment and resource settings An extra configuration file and service lifecycle to maintain

Security and reliability checks before publishing an image

  • Base image: Use a trusted, small Python image and update it under your project’s patching policy. Record the selected tag.
  • Dependencies: Pin versions and rebuild when security fixes are released. Keep the dependency file ahead of source-copy instructions for cache reuse.
  • Build separation: Use a builder and runtime stage so compilers and download artifacts do not ship to production.
  • Privileges: Create an unprivileged application user and verify that every required directory is readable or writable by that user.
  • Secrets: Pass credentials at runtime through environment variables or a secret manager; never bake them into layers.
  • Signals: Use JSON/exec-form CMD so the server receives container stop signals directly.
  • Health: Provide a lightweight endpoint and configure a health check when the hosting platform uses it.
  • Context: Keep virtual environments, caches, VCS metadata, local environment files and test artifacts out of the build context.
  • Resources: Set CPU and memory limits where your orchestrator supports them, then size Gunicorn workers within those limits.

Troubleshoot the failures that appear most often

The container starts, but the browser cannot connect

  • Confirm the server command uses --host 0.0.0.0 or Gunicorn’s --bind 0.0.0.0:8000.
  • Confirm the run command publishes the same container port declared by the server, for example -p 8000:8000.
  • Check logs with docker logs <container-name> and verify that the process did not exit during import.

Changes to requirements do not appear

Changing only application source can legitimately reuse the dependency layer. After editing the dependency file, rebuild; if you suspect an invalid cached layer, use docker build --no-cache -t my-python-app .. Keep the dependency copy before COPY . . so normal source edits remain fast.

The process reports “module not found”

Check the module path in the command, the package layout inside /app and whether the required file is excluded by .dockerignore. A command such as app:app requires an app.py file and an object named app.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A package fails while building

Some Python packages compile native extensions. Add the required compiler or operating-system headers only in the builder stage, then copy the resulting wheels into the runtime stage. If the package also needs a shared library when the application runs, install that runtime library in the second stage.

The application cannot write files

After switching to USER appuser, inspect ownership of application directories and mounted volumes. Copy files with --chown and mount a specifically writable data directory instead of reverting the entire container to root.

Make builds reproducible in CI and deployment

  1. Commit the Dockerfile, dependency lock or requirements file and .dockerignore.
  2. Build from a clean checkout so untracked local files cannot affect the image.
  3. Tag the image with an immutable release identifier in addition to any human-friendly tag.
  4. Run the container with the same port, environment contract and health endpoint expected in deployment.
  5. Scan and update the base image and dependencies according to your security process.
  6. Promote the built image rather than rebuilding separately in each environment; supply environment-specific configuration at runtime.

This workflow preserves the main advantages of containers: identical dependency resolution, explicit runtime behavior and a clear boundary between the packaged application and the environment that operates it.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More quests from Patch Notes

  1. How to Set Up a RedM (RDR2) Server in 2026: License Key, txAdmin and server.cfg, Step by StepBlog11min
  2. Best RedM (RDR2) Server Hosting in 2026: Comparing Four Hosts on Slots, Memory and PriceBlog10min
  3. How to Host a Mindustry Server in 2026: server-release.jar, Port 6567 and the Commands That MatterBlog7min
Recommended PC Tool
Recommended PC Tool
Windows Errors? Fix Them Before They SpreadFree repair scan
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.