Editor's Image | Mid-journey and Canva
Creating minimal Docker images for Python applications improves security by reducing the attack surface, facilitating faster image creation, and improving overall application maintainability. Let's learn how to create minimal Docker images for Python applications.
Previous requirements
Before starting:
- You should have Docker installed. Get Docker for your operating system if you haven't already.
- A sample Python application for which you need to create the minimal image. You can also follow the example application We create.
Create a sample Python application
Let's create a simple Flask application for inventory management. This app will allow you to add, view, update and delete inventory items. We will then dock 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 is the code for the Flask inventory management app:
# 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. Use Flask to create a web server that listens to HTTP requests on port 5000. It can send the following requests:
- A POST request for
/inventory
to add a new item to the inventory. - A GET request for the form
/inventory/
to retrieve the item with the specified ID from inventory. - A PUT request from the form
/inventory/
to update the item with the specified ID in the inventory. - A DELETE request like this:
/inventory/
to remove the item with the specified ID from inventory.
Now create the requirements.txt file:
Then 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 we 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
domain:
$ 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 use, the default Python 3.11 image, has a large number of Debian packages and is about 1.01 GB in size. Then we need to find a smaller base image.
Well, here are the options:
python:version-alpine
The images are based on Alpine Linux and will give you the smallest final image. But you also need to be able to install packages, right? But that's a challenge with alpine images.python:version-slim
comes with the minimum number of Debian packages needed to run Python. And (almost always) you will be able to install most of the necessary Python packages withpip
.
So your base image should be small. But not so small that you face compatibility issues and focus on installing dependencies (quite common for Python applications). That is why we will use the python:3.11-slim
base image in the next step and build our image.
Choose the optimal base image | Image by author
Use Slim Python base image
Now rewrite the Dockerfile to use the python:3.11-slim
base image like this:
# 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 (labeled slim
):
$ docker build -t inventory-app:slim .
He python:3.11-slim
The base image has a size of 131 MB. And the inventory-app:slim
The image is around 146 MB, which is much smaller than the 1.02 GB image we had before:
$ 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 reduce the size of the final image. But that's for another tutorial!
Additional Resources
Here are some useful resources:
twitter.com/balawc27″ rel=”noopener”>girl priya c is a developer and technical writer from India. He enjoys working at the intersection of mathematics, programming, data science, and content creation. His areas of interest and expertise include DevOps, data science, and natural language processing. He likes to read, write, code and drink coffee! Currently, he is working to learn and share his knowledge with the developer community by creating tutorials, how-to guides, opinion pieces, and more. Bala also creates engaging resource descriptions and coding tutorials.
<script async src="//platform.twitter.com/widgets.js” charset=”utf-8″>