DEV Community

Cover image for From Python Script to Docker Container: A Beginner's Guide (Windows + WSL2)
Raafe Asad
Raafe Asad

Posted on

From Python Script to Docker Container: A Beginner's Guide (Windows + WSL2)

No prior Docker experience needed, just a Windows PC, Python, and curiosity!

What We Will Achieve in this Blog

We have a simple Python script that:

  • Loads an image,
  • Boosts its contrast using the Pillow library,
  • Saves the enhanced version.

We want to dockerize it — that is, package it into a self-contained, portable “container” that runs anywhere (a laptop, a colleague’s machine, the cloud), without worrying about Python versions or missing libraries.

And we’re on Windows, using Docker Desktop and WSL 2 — the recommended setup for modern Docker development on Windows.

Let's deep dive into a step by step guide on dockerizing the python script.

Part 1: The Python Script

Here is a clean and straightforward script for image enhancement:

from PIL import Image, ImageEnhance
import sys
import os

def enhance_image(input_path: str, output_path: str):
    img = Image.open(input_path)
    enhancer = ImageEnhance.Contrast(img)
    img_enhanced = enhancer.enhance(1.5)  # 50% more contrast
    img_enhanced.save(output_path)
    print(f"Processed {input_path}{output_path}")

if __name__ == "__main__":
    if len(sys.argv) != 3:
        print("Usage: python image_enhance.py <input.jpg> <output.jpg>")
        sys.exit(1)
    enhance_image(sys.argv[1], sys.argv[2])
Enter fullscreen mode Exit fullscreen mode

It uses command-line arguments for the sake of removing hard-coded filepaths for input and output folders. In addition, it only needs Pillow, and does not require any heavy dependencies. This setup is ideal for our use case.

Part 2: Dockerizing the Script in 3 Files

Create a new folder (eg., image_enhancement) and add these three files:

  1. requirements.txt
Pillow==10.4.0
Enter fullscreen mode Exit fullscreen mode

The requirements file tells Docker which python packages to install. It is better if we specify the version of the required libraries for coherence.

  1. Dockerfile

This Dockerfile defines a secure, efficient, and well-structured environment for running the Python script. Let's look at a breakdown of what a typical Dockerfile structure for Python looks like:

# Stage 1: Define the base image
FROM python:3.11-slim

# Stage 2: Set the working directory
WORKDIR /app

# Stage 3: Copy dependency files (if any)
COPY requirements.txt .

# Stage 4: Install dependencies (if any)
RUN pip install --no-cache-dir -r requirements.txt

# Stage 5: Copy the application script
COPY your_script.py .

# Stage 6: Command to run the application
CMD ["python", "your_script.py"]
Enter fullscreen mode Exit fullscreen mode

Here's what each instruction does:

  1. FROM -> Specifies the base image for our container. It's the starting point that includes the necessary operating system and software.

  2. WORKDIR -> Sets the working directory for any subsequent RUN, CMD, ENTRYPOINT, COPY, or ADD instructions. All file paths will be relative to this directory.

  3. COPY -> Copies files from our local build context, i.e., the directory where the Dockerfile is located, into the container's filesystem at the WORKDIR.

  4. RUN -> Executes any commands in a new layer on top of the current image. This is typically used for installing packages or setting up the environment.

  5. CMD -> Provides defaults for an executing container. This is the command that runs when the container starts. Only the last CMD instruction in a Dockerfile will take effect.

For our use case, this Dockerfile is created:

# Stage 1: Build Image
FROM python:3.12-slim AS base

# Non-root user for security compliance
RUN useradd --create-home appuser && chown -R appuser:appuser /home/appuser
WORKDIR /home/appuser
USER appuser

# Copy requirements for better layer caching
COPY requirements.txt .
RUN pip install --no-cache-dir --disable-pip-version-check -r requirements.txt

# Copy the script
COPY image_enhance.py .

# Set entrypoint as a list form + CMD to accept args
ENTRYPOINT ["python", "image_enhance.py"]
# CMD will be overridden by docker run args — so leave empty or set defaults
CMD ["--help"]
Enter fullscreen mode Exit fullscreen mode

You may note that some addtional instructions have been passed in our use case. So, here is a breakdown of why they were included in the Dockerfile:

  1. FROM python:3.12-slim AS base -> Uses a small, production-ready base image that includes Python 3.12, minimizing the final image size.

  2. RUN useradd --create-home appuser && chown -R appuser:appuser /home/appuser -> Creates a dedicated, non-root user named appuser and sets up a home directory. This is a crucial security best practice to prevent potential compromises from running the application as the powerful root user.

  3. WORKDIR /home/appuser -> Sets the working directory to the new user's home directory.

  4. USER appuser -> Switches the context so all subsequent commands (COPY, RUN, ENTRYPOINT, CMD) are executed as the non-root appuser.

  5. COPY requirements.txt . -> Copies the file listing external Python packages into the container.

  6. RUN pip install --no-cache-dir --disable-pip-version-check -r requirements.txt -> Installs all dependencies listed in requirements.txt using pip. The --no-cache-dir option reduces the final image size by discarding cached installation files.

  7. COPY image_enhance.py . -> Copies te main Python script into the working directory.

  8. ENTRYPOINT ["python", "image_enhance.py"] -> This sets the main executable for the container. The list form ensures that the Python interpreter is executed directly. Crucially, it makes the container behave like an executable command.

  9. CMD ["--help"] -> This sets the default arguments to be passed to the ENTRYPOINT.

Combined Execution Logic

On running the container without any arguments, the full command executed is:

python image_enhance.py --help
Enter fullscreen mode Exit fullscreen mode

Part 3: Building the Container

To build the container image, Open PowerShell or Command Prompt, go the project folder, and run:

docker build -t image-enhancer .
Enter fullscreen mode Exit fullscreen mode

This command instructs Docker to download Python, install Pillow, and package the script. When it finishes, a read-to-run container image called image-enhancer has been built.

If we are to type docker images in the terminal, it will list all the container images. You should see image-enhancer there.

Part 4: Running it with Real Files

The project directory (from which you built the docker image) should look like this:

image_enhancer/
├── input/
│   └── chicken.jpg     ← put any image here
├── output/             ← leave this empty
├── image_enhance.py
├── requirements.txt
└── Dockerfile
Enter fullscreen mode Exit fullscreen mode

In this directory, open PowerShell or Command Prompt, and run the container by:

docker run --rm `
  -v "${PWD}/input:/input:ro" `
  -v "${PWD}/output:/output" `
  image-enhancer `
  /input/chicken.jpg /output/chicken_enhanced.jpg
Enter fullscreen mode Exit fullscreen mode

On running this command, you will see:

Processed /input/chicken.jpg → /output/chicken_enhanced.jpg

These commands look complicated at the first attempt, so let's unravel what we just typed in the terminal:

  1. docker run -> This is the core command for executing anything in a new container. A docker container cannot be executed without these commands.

  2. --rm -> This is a flag to automatically remove the container filesystem when the container exists. Usually, a docker container acquires more volume than expected, so, adding this flag in the command keeps the system clean after execution.

  3. \ -> This is added for line continuation. This is primarily used in shells to break a long command into multiple lines for readability.

  4. -v "${PWD}/input:/input:ro" -> This command mounts the local machine's ${PWD}/input directory (where ${PWD} is the current working directory) to the container's /input directory. the :ro (read-only) flag ensures the container cannot accidentally modify the original input files.

  5. -v "${PWD}/output:/output" -> This mounts the local machine's ${PWD}/outputdirectory to the container's /output directory. The container will write its results (the enhanced images) here.

  6. image-enhancer -> This specifies the Docker image to use for running the container. This is the image we built from the Dockerfile.

  7. /input/chicken.jpg -> This is the first container argument passed to the container's ENTRYPOINT (python image_enhance.py). It tells the script which file inside the mounted input directory to process.

  8. /output/chicken_enhanced.jpg -> this is the second argument passed to the container's ENTRYPOINT. It tells the script where to save the resulting enhanced image file inside the mounted output directory.

How Are These Commands Related to the Dockerfile

This command perfectly uses the design you saw in the Dockerfile:

ENTRYPOINT ["python", "image_enhance.py"] -> The base command executed is python image_enhance.py.

Arguments: The container arguments (/input/chicken.jpg and /output/chicken_enhanced.jpg) replace the default CMD ["--help"] and are appended to the ENTRYPOINT.

The full command executed inside the container will be:

python image_enhance.py /input/chicken.jpg /output/chicken_enhanced.jpg
Enter fullscreen mode Exit fullscreen mode

This makes your containerized application behave exactly like a command-line tool running directly on your host machine.

Part 5: Fixing Any Errors in WSL2

If you tried this in WSL2 and got:

The command 'docker' could not be found in this WSL 2 distro.

Don't panic, this is a common issue in WSL2. Let's see how we can fix it:

  1. Open Docker Desktop (from Windows Start menu).
  2. Go to SettingsResourcesWSL Integration.
  3. Check the box next to your Linux distro.
  4. Click Apply & Restart.

WSL Integration

You can verify its working by:

Open your WSL terminal and run:

docker --version
# Should print: Docker version 28.x.x…
Enter fullscreen mode Exit fullscreen mode

WSL Integration

Now you can build and run containers inside WSL — often faster and more natural for Linux-based workflows!

Next Steps (When You’re Ready)

  • Automate it: Write a .bat or .ps1 script to run the command with one click.
  • Connect a camera: Pull an image via HTTP, enhance it, save it — all automated.
  • Batch process: Modify the script to enhance all images in a folder.
  • Tweak contrast: Add a --contrast 2.0 flag using argparse.

Key Takeaways

  1. Portability -> Share your tool with teammates, no “but it works on my machine!”
  2. Reproducibility -> Exact Python/Pillow versions — no surprises.
  3. Isolation -> Your system stays clean; no global pip installs.
  4. Scalability -> Later, deploy the same container to servers or cloud.

Containers might seem intimidating at first, but as you’ve seen, dockerizing a simple tool takes just 3 files and 2 commands. Once set up, you gain reliability, portability, and the ability to ship your code with confidence.

And with Docker Desktop + WSL 2 on Windows? You get the best of both worlds: Windows comfort and Linux power.

Happy coding, and happy enhancing!!

Top comments (0)