Image by Editor | Midjourney & Canva
Creating minimal Docker images for Python apps enhances security by reducing the attack surface, facilitates faster image builds, and improves overall application maintainability. Let’s learn how to create minimal Docker images for Python applications.
Prerequisites
Before you get started:
- You should have Docker installed. Get Docker for your operating system if you haven’t already.
- A sample Python application you need to build the minimal image for. You can also follow along with the example app we create.
Create a Sample Python Application
Let’s create a simple Flask application for inventory management. This application will allow you to add, view, update, and delete inventory items. We’ll then dockerize the application using the standard Python 3.11 image.
In your project directory, you should have app.py, requirements.txt, and Dockerfile:
inventory_app/
├── app.py
├── Dockerfile
├── requirements.txt
Here’s the code for the Flask app for inventory management:
# app.py
from flask import Flask, request, jsonify
app = Flask(__name__)
# In-memory database for simplicity
inventory = {}
@app.route('/inventory', methods=['POST'])
def add_item():
item = request.get_json()
item_id = item.get('id')
if not item_id:
return jsonify({"error": "Item ID is required"}), 400
if item_id in inventory:
return jsonify({"error": "Item already exists"}), 400
inventory[item_id] = item
return jsonify(item), 201
@app.route('/inventory/', methods=['GET'])
def get_item(item_id):
item = inventory.get(item_id)
if not item:
return jsonify({"error": "Item not found"}), 404
return jsonify(item)
@app.route('/inventory/', methods=['PUT'])
def update_item(item_id):
if item_id not in inventory:
return jsonify({"error": "Item not found"}), 404
updated_item = request.get_json()
inventory[item_id] = updated_item
return jsonify(updated_item)
@app.route('/inventory/', methods=['DELETE'])
def delete_item(item_id):
if item_id not in inventory:
return jsonify({"error": "Item not found"}), 404
del inventory[item_id]
return '', 204
if __name__ == '__main__':
app.run(host="0.0.0.0", port=5000)
This is a minimal Flask application that implements basic CRUD (Create, Read, Update, Delete) operations for an in-memory inventory database. It uses Flask to create a web server that listens for HTTP requests on port 5000. When a request is received:
- For a POST request to
/inventory
, it adds a new item to the inventory. - For a GET request to
/inventory/<item_id>
, it retrieves the item with the specified ID from the inventory. - For a PUT request to
/inventory/<item_id>
, it updates the item with the specified ID in the inventory. - For a DELETE request to
/inventory/<item_id>
, it deletes the item with the specified ID from the inventory.
Now create the requirements.txt file:
Next create the Dockerfile:
# Use the official Python 3.11 image
FROM python:3.11
# Set the working directory
WORKDIR /app
# Install dependencies
COPY requirements.txt requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
# Copy the current directory contents into the container at /app
COPY . .
# Expose the port the app runs on
EXPOSE 5000
# Run the application
CMD ["python3", "app.py"]
Finally build the image (we use the tag full
to identify that this uses the default Python image):
$ docker build -t inventory-app:full .
Once the build is complete you can run the docker images
command:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
inventory-app full 4e623743f556 2 hours ago 1.02GB
You’ll see that this super simple app is about 1.02 GB in size. Well, this is because the base image we used the default Python 3.11 image has a large number of Debian packages and is about 1.01 GB in size. So we need to find a smaller base image.
Well, here are the options:
python:version-alpine
images are based on Alpine Linux and will give you the smallest final image. But you need to be able to install packages as well, yes? But that’s a challenge with alpine images.python:version-slim
comes with the minimal number of Debian packages needed to run Python. And you’ll (almost always) be able to install most required Python packages withpip
.
So your base image should be small. But not too small that you face compatibility issues and wrap your head around installing dependencies (quite common for Python applications). That’s why we’ll use the python:3.11-slim
base image in the next step and build our image.
Choosing the Optimal Base Image | Image by Author
Use the Slim Python Base Image
Now rewrite the Dockerfile to use the python:3.11-slim
base image like so:
# Use the official lightweight Python 3.11-slim image
FROM python:3.11-slim
# Set the working directory
WORKDIR /app
# Install dependencies
COPY requirements.txt requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
# Copy the current directory contents into the container at /app
COPY . .
# Expose the port the app runs on
EXPOSE 5000
# Run the application
CMD ["python3", "app.py"]
Let’s build the image (tagged slim
):
$ docker build -t inventory-app:slim .
The python:3.11-slim
base image is of size 131 MB. And the inventory-app:slim
image is around 146 MB which is much smaller than the 1.02GB image we had earlier:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
inventory-app slim 32784c60a992 About an hour ago 146MB
inventory-app full 4e623743f556 2 hours ago 1.02GB
You can also use multi-stage builds to make the final image smaller. But that’s for another tutorial!
Additional Resources
Here are a few useful resources:
Bala Priya C is a developer and technical writer from India. She likes working at the intersection of math, programming, data science, and content creation. Her areas of interest and expertise include DevOps, data science, and natural language processing. She enjoys reading, writing, coding, and coffee! Currently, she’s working on learning and sharing her knowledge with the developer community by authoring tutorials, how-to guides, opinion pieces, and more. Bala also creates engaging resource overviews and coding tutorials.