skip to Main Content

I just create a single stage dockerfile in my basic CRUD application.
I want to multistaging because of reducing space of my docker image.
Dockerfile

FROM python:3.9-slim

# Set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# Set the working directory in the container
WORKDIR /app

# Copy the dependencies file to the working directory
COPY requirements.txt .

RUN apt-get update && apt-get install -y default-mysql-client netcat-traditional && rm -rf /var/lib/apt/lists/*

# Install dependencies
RUN pip3 install -r requirements.txt

# Copy the content of the local src directory to the working directory
COPY . .
COPY wait-for.sh .
# Expose port 8000 to the outside world
EXPOSE 8000
RUN chmod +x wait-for.sh
CMD ["uvicorn", "index:app", "--reload", "--host", "0.0.0.0", "--port", "8000"]

So I do this in my Dockerfile
Dockerfile.dev

FROM python:3.9-slim as BUILD
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
WORKDIR /app

COPY requirements.txt .
RUN pip install -r requirements.txt
RUN apt-get update && apt-get install -y default-mysql-client netcat-traditional && rm -rf /var/lib/apt/lists/*
COPY . .

#CMD
FROM python:3.9-slim

COPY --from=BUILD . .
COPY wait-for.sh .
# Expose port 8000 to the outside world
EXPOSE 8000

RUN chmod +x wait-for.sh
CMD ["uvicorn", "index:app", "--reload", "--host", "0.0.0.0", "--port", "8000"]

But the second step cannot hold the index.py file.

docker-compose.yml

version: '3.8'

services:
  app:
    build: 
      context: .
      dockerfile: Dockerfile.dev #Here change the Dockerfile and Dockerfile.dev 
                                 #Dockerfile for single stage and Dockerfile.dev for multistage
    ports:
      - 8000:8000
    # restart: on-failure
    environment:
      DATABASE_HOST: db
      DATABASE_USER: root
      DATABASE_PASSWORD: 12345678
      DATABASE_NAME: Student
    depends_on:
      - db
    entrypoint: [ "./wait-for.sh", "db:3306", "--", "uvicorn", "index:app", "--reload", "--host", "0.0.0.0", "--port", "8000" ]
  db:
    image: mysql:5.7
    volumes:
      - ./my.cnf:/etc/mysql/my.cnf
    environment:
      MYSQL_USER: user
      MYSQL_ROOT_PASSWORD: 12345678
      MYSQL_DATABASE: Student
      MYSQL_PASSWORD: 12345678
    ports:
      - "3307:3306"
 

My question is "is it ever possible to dockerize my fastapi application with multistage docker?"
If it is possible how can I do this. Also If it is not possible then why can’t I do this.
What will be the entrypoint of this?

2

Answers


  1. The image you’ve shown won’t benefit from a multistage build.

    The setups where multistage builds save space are if there are tools you need to build your image, but not to actually run the container. For a Python application, libraries with C extensions need the C compiler, which is quite large, but only at installation time. For those sorts of applications, the first stage can install the compiler and C header files, but the second stage only needs the corresponding runtime libraries.

    An example Python-based Dockerfile could look like this. Note that we’re using the exact same base image for both the build and runtime stage, and the virtual environment is in the same directory. The first stage installs build-essential, which includes a full C toolchain, but is quite large; the second stage doesn’t need that.

    ARG PYTHON=python:3.12-slim
    FROM $PYTHON AS build
    
    # Install (large) build-time dependencies
    RUN apt-get update 
     && DEBIAN_FRONTEND=noninteractive 
        apt-get install --no-install-recommends --assume-yes 
          build-essential 
          libpq-dev
    
    # Create a virtual environment that can be copied into the next stage
    RUN python -m venv /venv
    
    # Install packages into it
    WORKDIR /app
    COPY requirements.txt ./
    RUN /venv/bin/pip install -r requirements.txt
    
    FROM $PYTHON
    
    # Install only the libraries you need to run the application
    RUN apt-get update 
     && DEBIAN_FRONTEND=noninteractive 
        apt-get install --no-install-recommends --assume-yes 
          libpq5
    
    # Copy the virtual environment from the first stage
    COPY --from=build /venv/ /venv/
    ENV PATH=/venv/bin:$PATH
    
    # The rest of your Dockerfile as before
    ...
    CMD ["uvicorn", ...]
    

    You also asked about a correct value for an entrypoint. This doesn’t change in a multistage build, noting that only the value from the final image is used.

    You shouldn’t normally need to override the entrypoint: in the Compose file, and many cases do not need to override command: either. I might move the call to wait-for.sh into the Dockerfile

    ENTRYPOINT ["./wait-for.sh", "db:3306", "--"]
    CMD ["uvicorn", ...]
    

    A more robust solution could be to wrap it in a dedicated shell script, that honors the deploy-time database hostname

    #!/bin/sh
    
    # Wait for the database to be ready
    if [ -n "$DATABASE_HOST" ]; then
      ./wait-for.sh "$DATABASE_HOST:3306"
    fi
    
    # Switch to the main container command
    exec "$@"
    

    This script can be the image’s ENTRYPOINT (make sure to use JSON-array syntax), and you don’t need to put anything special in the Compose file.

    Login or Signup to reply.
  2. Yes, you can create docker image with multistage-build for application that uses fastAPI. For your second question the entry point will remain the same.

    Although for cases like this multi-stage build is likely to provide less value (save less space) for interpreted languages like python as your specific case. If you still wish to approach this, You can approach it in a similar way for creating different stages for build and runtime environment for compiled languages.

    You can consider the below example:

    # Stage 1: Build environment
    FROM python:3.9 AS builder
    
    WORKDIR /app
    COPY requirements.txt .
    
    # Install dependencies
    RUN pip install --no-cache-dir -r requirements.txt
    

    For the second stage you can create a runtime environment for your purpose.

    # Stage 2: Runtime environment
    FROM python:3.9-slim
    
    WORKDIR /app
    COPY --from=builder /app /app
    
    # Copy your FastAPI app code
    COPY . .
    
    # Set the entry point
    CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
    

    keep in mind that multistage builds usually benefit cases where you need to build the container without running or running the container doesn’t require entirety of the build tools. David Maze‘s answer is more appropriate for you specific use case.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search