Containerize a Python API with Docker

Package a Python Flask REST API into a portable Docker container.

Introduction

30 Second Summary

"It works on my machine" is the most common excuse in software development. Containers solve this by packaging your app with everything it needs to run identically on any machine.

In this project, you will build a Python Flask REST API, package it into a Docker container, and learn to manage the full container lifecycle.

What You'll Build

A Python Flask REST API that returns server health and system info, fully containerized in Docker.

By the end of this project, you'll have:

  • A Flask REST API with multiple endpoints for health checks and system info.
  • A production-style Dockerfile using best practices like layer caching and .dockerignore.
  • Hands-on experience building, running, stopping, and removing Docker containers from the command line.
  • Secret Mission: implement a multi-stage build to shrink your image size and compare the results.

Are there any prerequisites?

None. This guide includes full setup instructions for Docker Desktop and VS Code. You do not need prior experience with Docker or Flask.

Install Docker Desktop and VS Code

Before you can containerize anything, you need the tools that make containers possible. Docker Desktop is the application that runs the Docker engine on your Mac, and VS Code is the code editor you will use to write your API and Dockerfile.

In this step, you will install both tools and confirm that Docker can pull images and run containers on your machine.

In this step, get ready to:
  • Install Docker Desktop and verify the engine is running.
  • Run your first container to confirm everything works.
  • Install VS Code for editing project files.
Install Docker Desktop

Docker Desktop is the app that runs the Docker engine on macOS. It includes the Docker CLI, Docker Compose, and a graphical dashboard for managing containers.

  • Open your browser and go to the Docker Desktop install page.
  • Click the download button for your Mac type (Apple silicon or Intel chip).
  • Once the .dmg file downloads, double-click it to open the installer.
  • Drag the Docker icon into the Applications folder.
  • Press Cmd+Space to open Spotlight search.
  • Type Docker and press Enter to launch Docker Desktop.
  • Accept the Docker Subscription Service Agreement when prompted.
  • Select Use recommended settings when asked about configuration.
  • Wait for the Docker engine to start. The whale icon in your menu bar will stop animating when the engine is ready.

Is Docker Desktop free?

Docker Desktop is free for personal use, education, and non-commercial open source projects. It is also free for small businesses with fewer than 250 employees AND less than $10 million in annual revenue.

Verify Docker is working

With Docker Desktop running, you can now test that the Docker CLI is available and that the engine can pull and run containers.

  • Open Terminal by pressing Cmd+Space and typing Terminal, then pressing Enter.
  • Check that the Docker CLI is installed by running this command:
docker --version

✔️ I see a version number

Docker is installed and available in your terminal. Move on to the next command below.

ⓧ Command not found

This means Docker Desktop has not finished setting up the CLI tools. Try these fixes:

  • Make sure Docker Desktop is running (check for the whale icon in your menu bar).
  • Close and reopen your Terminal window so it picks up new PATH settings.
  • If you still see the error, restart Docker Desktop from the whale icon menu by clicking Restart.
  • Confirm Docker can pull images and run containers by running:
docker run hello-world

✔️ I see Hello from Docker!

Your Docker installation is fully working. The engine pulled the hello-world image from Docker Hub and ran it as a container.

ⓧ I see a permission or connection error

This usually means the Docker engine has not finished starting. Try these fixes:

  • Wait 30 seconds for the whale icon to stop animating, then try the command again.
  • If you see a message about permissions, open Docker Desktop, go to Settings > General, and make sure the Docker socket option is enabled.
  • Restart Docker Desktop and wait for the engine to fully start before retrying.

What did docker run hello-world do?

This command told Docker to run a container from the hello-world image. Since the image was not on your machine yet, Docker automatically pulled it from Docker Hub (a public registry of container images) and then ran it.

The output message confirms that the Docker client, daemon, and network connection to Docker Hub are all working correctly.

Install VS Code

VS Code is a free code editor that you will use to write your Python API and Dockerfile.

✔️ I already have VS Code

You are all set. Skip ahead to the next step.

ⓧ I need to install it

  • Open your browser and go to code.visualstudio.com.
  • Click the download button for macOS. This downloads a .zip file.
  • Open your Downloads folder and double-click the .zip file to extract it.
  • Drag Visual Studio Code.app into your Applications folder.
  • Press Cmd+Space, type Visual Studio Code, and press Enter to open it.

You should see the VS Code welcome tab open in a new window.

Your tools are ready. Next up, you will write the Flask API that you will eventually containerize with Docker.

Create the Flask API

Before you can containerize anything, you need an application to put inside the container. In this step, you will build a small Python API from scratch.

The API uses Flask, a lightweight Python web framework. It exposes three endpoints that return JSON data about the server's health, system info, and runtime status.

In this step, get ready to:
  • Create a project directory and open it in VS Code.
  • Write a Flask API with three endpoints.
  • Test the API locally before containerizing it.
Create the project directory

Every project needs a home folder. You will create a dedicated directory on your Desktop so you can find it easily later.

  • Open Terminal by pressing Cmd+Space (macOS), typing Terminal, and pressing Enter.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir devops-api
cd devops-api
  • Confirm the folder exists by running:
ls ~/Desktop | grep devops-api

You should see devops-api in the output.

  • Open this folder in VS Code by pressing Cmd+Space, typing Visual Studio Code, and pressing Enter.
  • In VS Code, click File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop, select the devops-api folder, and click Open.

Don't see the devops-api folder?

Make sure you ran both mkdir devops-api and that your terminal was on the Desktop when you ran it. Run ls ~/Desktop to list everything on your Desktop and look for devops-api.

Help me find my devops-api project folder

Write the Flask application

Your API will live in a single file called app.py. You will build it up piece by piece so you understand each section before moving on.

  • In the VS Code Explorer sidebar (left panel), click the New File icon.
  • Name the file app.py and press Enter.
  • Add the application setup code by copying the following into app.py:
import os
import sys
import time
from flask import Flask, jsonify

app = Flask(__name__)

start_time = time.time()
request_count = 0


@app.before_request
def count_requests():
    global request_count
    request_count += 1

What does this code do?

  • The imports bring in modules for reading environment variables (os), system info (sys), and time tracking (time).
  • Flask creates the web app, and jsonify turns Python dictionaries into JSON responses.
  • start_time records when the server started so you can calculate uptime later.
  • The count_requests() function runs before every incoming request and increments the counter by one.
  • Add the first endpoint below the code you just pasted, leaving two blank lines between the previous function and this new one:
@app.route("/health")
def health():
    return jsonify({"status": "healthy", "version": "1.0"})

What does this endpoint do?

The /health route is a simple heartbeat check. It returns a JSON object confirming the server is up and which version is running.

  • Add the second endpoint below health(), again leaving two blank lines above it:
@app.route("/info")
def info():
    return jsonify({
        "hostname": os.environ.get("HOSTNAME", "unknown"),
        "python_version": sys.version,
        "platform": sys.platform
    })

What does this endpoint do?

The /info route reports system details: the machine's hostname, the Python version, and the operating system platform. This becomes especially interesting once the app runs inside a container, because the hostname will be the container's ID instead of your Mac's name.

  • Add the third endpoint and the entry point below info(), leaving two blank lines above it:
@app.route("/api/status")
def status():
    uptime = time.time() - start_time
    return jsonify({
        "uptime_seconds": round(uptime, 2),
        "request_count": request_count,
        "status": "running"
    })


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)

What does this code do?

  • The /api/status route returns how many seconds the server has been running and how many requests it has handled.
  • The if __name__ == "__main__" block starts the Flask development server on port 5000.
  • host="0.0.0.0" tells Flask to listen on all network interfaces, which is required for the app to be reachable from outside a container later.
  • Save app.py by pressing Cmd+S.

✔️ Awesome, I've got everything!

Great. Double check that you have saved app.py and move on to the next section.

ⓧ I'd like to double check the full code

Here is the complete app.py for comparison:

import os
import sys
import time
from flask import Flask, jsonify

app = Flask(__name__)

start_time = time.time()
request_count = 0


@app.before_request
def count_requests():
    global request_count
    request_count += 1


@app.route("/health")
def health():
    return jsonify({"status": "healthy", "version": "1.0"})


@app.route("/info")
def info():
    return jsonify({
        "hostname": os.environ.get("HOSTNAME", "unknown"),
        "python_version": sys.version,
        "platform": sys.platform
    })


@app.route("/api/status")
def status():
    uptime = time.time() - start_time
    return jsonify({
        "uptime_seconds": round(uptime, 2),
        "request_count": request_count,
        "status": "running"
    })


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)
Add dependencies and test locally

Flask is an external package, so you need to list it as a dependency. You will also run the app on your Mac first to confirm it works before putting it in a container.

  • In VS Code, click the New File icon in the Explorer sidebar.
  • Name the file requirements.txt and press Enter.
  • Paste the following single line into requirements.txt:
Flask==3.1.3

Why pin a specific version?

Pinning Flask==3.1.3 ensures every person (and every container) that installs this project gets the exact same version. Without the pin, a future Flask release could introduce breaking changes.

  • Save requirements.txt by pressing Cmd+S.
  • Open the VS Code integrated terminal by pressing Ctrl+` (backtick key, top-left of your keyboard).
  • Create a Python virtual environment and install Flask by running these commands:
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

What do these commands do?

  • python3 -m venv venv creates an isolated Python environment in a folder called venv/.
  • source venv/bin/activate switches your terminal session to use that isolated environment.
  • pip install -r requirements.txt reads the file and installs Flask 3.1.3 into the virtual environment.

You should see output ending with a line like Successfully installed Flask-3.1.3 (along with a few sub-dependencies).

Seeing command not found for python3?

If your terminal prints something like zsh: command not found: python3, Python 3 is not installed on your Mac. Install it from python.org/downloads, then close and reopen your terminal before retrying.

Help me install Python 3 on my Mac so I can continue this project

  • Start the Flask development server by running:
python app.py

You should see output that includes Running on http://0.0.0.0:5000.

  • Open your browser and navigate to http://localhost:5000/health.

You should see a JSON response: {"status":"healthy","version":"1.0"}.

  • Go back to your terminal and press Ctrl+C to stop the server.

Browser shows "unable to connect"?

Make sure you see the Running on line in your terminal before opening the browser. If the terminal shows an error instead, check that you saved app.py and that your virtual environment is active (you should see (venv) at the start of your terminal prompt).

Help me debug why my Flask app is not responding at localhost:5000

Your API is confirmed working locally. Next up, you will write a Dockerfile to package this app into a portable container that runs anywhere.

Write the Dockerfile

Your Flask API works on your machine. But what happens when you want to run it on a teammate's laptop, a cloud server, or a CI pipeline? You would need to install Python, set up the virtual environment, and install dependencies all over again.

A Dockerfile solves this by describing exactly how to build a Docker image of your app. Think of it as a recipe that packages your code, dependencies, and runtime into a single portable unit.

In this step, get ready to:
  • Create a Dockerfile with best practices for layer caching.
  • Add a .dockerignore file to keep the build context lean.
Create the Dockerfile

A Dockerfile is a plain text file with instructions that Docker reads top-to-bottom. Each instruction creates a layer in the final image. The order matters because Docker caches layers to speed up rebuilds.

  • In VS Code, create a new file called Dockerfile in your project folder (no file extension).
  • Paste the following code into Dockerfile:
# Use a lightweight Python base image
FROM python:3.13-slim

# Set the working directory inside the container
WORKDIR /app

# Copy requirements first and install dependencies (layer caching)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy the rest of the application code
COPY . .

# Document the port the app listens on
EXPOSE 5000

# Run the Flask app when the container starts
CMD ["python", "app.py"]

What does this Dockerfile do?

  • FROM python:3.13-slim picks a lightweight Python base image. The slim variant is Debian-based but strips out extras like compilers and docs.
  • WORKDIR /app creates and switches to the /app directory inside the container. All following commands run from here.
  • COPY requirements.txt . copies only the requirements file first, then RUN pip install installs dependencies. This order means Docker can cache the installed packages and skip reinstalling them unless requirements.txt changes.
  • COPY . . copies the rest of your project files into the container.
  • EXPOSE 5000 documents which port the app uses. It does not publish the port automatically.
  • CMD ["python", "app.py"] sets the default command that runs when a container starts from this image.

Why copy requirements.txt separately?

Docker builds images layer by layer. If a layer has not changed since the last build, Docker reuses the cached version instead of rebuilding it. By copying requirements.txt before the rest of your code, the dependency-install layer stays cached even when you edit app.py.

This is the number one Dockerfile optimization for Python apps. Without it, every code change triggers a full pip install, which can take minutes for large projects.

  • Save Dockerfile.
  • Confirm the file exists by running this command in your terminal:
ls Dockerfile

You should see Dockerfile in the output.

Don't see the file?

  • Make sure you are in your project folder. Run pwd to check your current directory.
  • The file must be named exactly Dockerfile with a capital D and no file extension. Not dockerfile.txt or Dockerfile.docker.

Still stuck? Why isn't my Dockerfile showing up when I run ls?

✔️ Awesome, I've got everything!

Great. Make sure you have saved your Dockerfile before moving on.

ⓧ I'd like to double check the full code

Here is the complete Dockerfile:

FROM python:3.13-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 5000

CMD ["python", "app.py"]
Add a .dockerignore file

When Docker builds an image, it sends your entire project folder (the "build context") to the Docker engine. Files like your venv/ folder and Python cache are large and unnecessary inside the container.

A .dockerignore file tells Docker which files to skip. This makes builds faster and keeps your image smaller.

  • Create a new file called .dockerignore in your project folder (note the leading dot).
  • Paste the following into .dockerignore:
# Virtual environment (large, not needed in container)
venv/
# Python cache files
__pycache__/
*.pyc
# Git files (not needed for running the app)
.git/
.gitignore
# Documentation files
*.md

What does this exclude?

  • venv/ is your local virtual environment. The container installs its own fresh dependencies from requirements.txt.
  • __pycache__/ and *.pyc are compiled Python bytecode files. Python regenerates them automatically.
  • .git/ and .gitignore are version control files that the running app does not need.
  • *.md matches any Markdown documentation files. These are for humans, not containers.
  • Save .dockerignore.
  • Confirm the file exists by running:
ls -a | grep dockerignore

You should see .dockerignore in the output. Files starting with a dot are hidden by default, which is why you need the -a flag.

File not showing up?

  • Make sure the filename starts with a dot: .dockerignore, not dockerignore.
  • Some editors may warn you about creating hidden files. Accept the warning and save the file normally.
  • Confirm you are in the project folder by running pwd.

Need more help? Why can't I see my .dockerignore file in the terminal?

✔️ Awesome, I've got everything!

Great. Make sure you have saved your .dockerignore before moving on.

ⓧ I'd like to double check the full code

Here is the complete .dockerignore:

venv/
__pycache__/
*.pyc
.git/
.gitignore
*.md

Your Dockerfile is ready to go. Next up, you will build this Dockerfile into a Docker image and run your API inside a container for the first time.

Build and Run Your Container

Your Dockerfile is ready to go. It contains the full recipe for packaging your Flask API into a portable Docker image.

Now it is time to use that recipe. In this step, you will build a Docker image from your Dockerfile, run a container from that image, and poke around inside it to see how everything works.

In this step, get ready to:
  • Build your Docker image from the Dockerfile.
  • Run a container and verify your API responds.
  • Inspect the running container using Docker commands.
Build your Docker image

A Docker image is a snapshot of your application and all its dependencies, frozen into a single package. Building the image reads your Dockerfile line by line and executes each instruction as a separate layer.

  • Make sure your terminal is in your project folder (the one containing your Dockerfile).
  • Build your Docker image by running this command:
docker build -t devops-api:1.0 .

What does this command do?

  • docker build reads your Dockerfile and creates an image from it.
  • -t devops-api:1.0 tags (names) the image as devops-api with version 1.0.
  • The . at the end tells Docker to use the current directory as the build context (all the files Docker can access during the build).
  • Watch the terminal output as Docker processes each layer. You should see it step through each instruction in your Dockerfile and finish with a success message showing the image ID.

Why does requirements.txt get its own layer?

Docker caches each layer. If a layer has not changed since the last build, Docker reuses the cached version instead of rebuilding it.

By copying requirements.txt and installing dependencies BEFORE copying your app code, Docker only re-installs packages when your dependencies actually change. If you only edit app.py, the dependency layer stays cached and your rebuild finishes in seconds.

Build failing?

  • Make sure Docker Desktop is running. The whale icon in your menu bar should be steady (not animating).
  • Confirm you are in the correct directory. Run ls and verify you can see Dockerfile, app.py, and requirements.txt in the output.
  • Check that your Dockerfile has no file extension. It should be named exactly Dockerfile with a capital D and no .txt suffix.

Help me debug my Docker build error

Run the container and verify your API

An image is a blueprint. A container is a live, running instance of that blueprint. You can run multiple containers from the same image.

  • Start a container from your image by running this command:
docker run -d -p 5000:5000 --name my-api devops-api:1.0

What do these flags mean?

  • -d runs the container in detached mode (in the background), so you get your terminal back.
  • -p 5000:5000 maps port 5000 on your Mac to port 5000 inside the container. Without this, the container's network is isolated and you cannot reach the API.
  • --name my-api gives the container a friendly name so you can reference it easily instead of using the container ID.
  • You should see a long string of characters printed in your terminal. That is the container ID, confirming the container started successfully.
  • Verify your API is responding by running this command:
curl http://localhost:5000/health
  • You should see a JSON response: {"status":"healthy","version":"1.0"}.
  • Try the info endpoint too by running:
curl http://localhost:5000/info
  • This time you should see a JSON response with the container's hostname, Python version, and platform. Notice the hostname is a random string. That is the container's own isolated hostname, not your Mac's.

Not getting a response?

  • Make sure the container is actually running. Run docker ps and check that my-api appears in the list.
  • If you see nothing in docker ps, the container may have crashed on startup. Run docker logs my-api to see error messages.
  • If port 5000 is already in use (from running the app locally in the previous step), stop the local Flask process first with Ctrl+C in that terminal, then try the docker run command again.

Help me troubleshoot my Docker container not responding

Inspect the running container

Your container is running in the background. Docker gives you several commands to peek inside and see what is happening without stopping it.

  • List all running containers by running:
docker ps
  • You should see a table showing your my-api container with its image name, uptime, and port mapping.
  • View the container's log output by running:
docker logs my-api
  • You should see Flask's startup message and any HTTP requests you made with curl logged here.
  • Open an interactive shell inside the running container by running:
docker exec -it my-api /bin/bash

What is happening here?

docker exec runs a command inside a running container. The -it flags give you an interactive terminal. You are now inside the container's filesystem, completely isolated from your Mac.

Try running ls to see your app files, or python --version to confirm the Python version. This is the exact environment your app runs in.

  • When you are done exploring, type exit and press Enter to leave the container shell and return to your normal terminal.

Shell command not working?

  • If you see a "no such container" message, confirm the container is still running with docker ps.
  • If /bin/bash fails, try /bin/sh instead. Some slimmer images only include the basic shell.

Help me get a shell inside my Docker container

Your API is running inside a container, accessible from your browser, and you know how to inspect it. Next up, you will learn to manage the full container lifecycle by stopping, restarting, rebuilding, and cleaning up containers.

Manage the Container Lifecycle

Your container is running and serving your API to the outside world. In a real deployment, containers don't run forever. You need to stop them for maintenance, restart them after updates, and replace them with new versions when your code changes.

In this step, you'll practice the core lifecycle commands that let you manage running containers. You'll also update your code, rebuild the image, and see Docker's layer caching in action.

In this step, get ready to:
  • Stop and restart your running container.
  • Rebuild the image after a code change.
  • Clean up old containers and images.
Stop and restart your container

A stopped container keeps all its data and configuration intact. You can restart it at any time without rebuilding the image.

  • Stop your running container by running this command:
docker stop my-api
  • Verify the container is no longer running by running:
docker ps

You should see an empty table with no containers listed. The my-api container still exists on your system, but it is no longer active.

  • Restart the container by running:
docker start my-api
  • Confirm it is accessible again by running:
curl http://localhost:5000/health

You should see the JSON response {"status": "healthy", "version": "1.0"} confirming your container is back up and serving requests.

Not seeing a response?

Give the container a few seconds to start up after running docker start. Run docker ps to confirm it shows my-api in the list before trying the curl command again.

help me debug why my Docker container isn't responding after docker start

Update your app and rebuild the image

When you change your application code, the running container does not pick up the changes automatically. You need to rebuild the image and start a new container from it.

  • In VS Code, open app.py and find the health() function.
  • Find this line:
    return jsonify({"status": "healthy", "version": "1.0"})
  • Change "1.0" to "1.1", so the line now looks like this:
    return jsonify({"status": "healthy", "version": "1.1"})
  • Save app.py.

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before continuing.

ⓧ I'd like to double check the full code

import os
import sys
import time
from flask import Flask, jsonify

app = Flask(__name__)

start_time = time.time()
request_count = 0


@app.before_request
def count_requests():
    global request_count
    request_count += 1


@app.route("/health")
def health():
    return jsonify({"status": "healthy", "version": "1.1"})


@app.route("/info")
def info():
    return jsonify({
        "hostname": os.environ.get("HOSTNAME", "unknown"),
        "python_version": sys.version,
        "platform": sys.platform
    })


@app.route("/api/status")
def status():
    uptime = time.time() - start_time
    return jsonify({
        "uptime_seconds": round(uptime, 2),
        "request_count": request_count,
        "status": "running"
    })


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)

Now you need to stop and remove the old container before you can reuse the name my-api. Docker does not allow two containers with the same name, even if one is stopped.

  • Stop and remove the old container by running:
docker stop my-api && docker rm my-api

Why stop AND remove?

docker stop halts the container but keeps it registered on your system. docker rm deletes the stopped container entirely, freeing up the name so you can reuse it for the new version.

  • Build the new image with the tag devops-api:1.1 by running:
docker build -t devops-api:1.1 .

What should I see?

Watch the build output closely. The lines for COPY requirements.txt and RUN pip install should show CACHED next to them. Docker knows those layers have not changed since your last build, so it skips them entirely.

Only the COPY . . layer and everything after it rebuilds, because your app code changed. This is exactly why the Dockerfile copies requirements.txt separately. It keeps dependency installation cached even when you change your source code.

  • Run a new container from the updated image by running:
docker run -d -p 5000:5000 --name my-api devops-api:1.1
  • Verify the new version is running by running:
curl http://localhost:5000/health

You should see {"status": "healthy", "version": "1.1"} in the response. The version number confirms your updated code is now running inside the new container.

Still seeing version 1.0?

Make sure you saved app.py before rebuilding. Docker copies files at build time, so unsaved changes are not included in the image.

Also confirm you ran the container from devops-api:1.1 (not devops-api:1.0). Check with docker ps to see which image your running container is using.

help me figure out why my Docker container is still showing the old version

Clean up containers and images

Leftover containers and images take up disk space over time. Cleaning up after yourself is a good habit that keeps your Docker environment tidy.

  • Stop and remove your running container by running:
docker stop my-api && docker rm my-api
  • View all Docker images on your system by running:
docker images

You should see both devops-api:1.0 and devops-api:1.1 listed along with the python:3.13-slim base image.

  • Remove the old version of your image by running:
docker rmi devops-api:1.0
  • Confirm it is gone by running docker images again.
docker images

You should see devops-api:1.1 still listed, but devops-api:1.0 is gone. Your Docker environment is now clean and ready for the next project.

Seeing an error when removing the image?

If you see a message about the image being referenced by a container, you still have a stopped container using that image. Run docker ps -a to find it, then remove it with docker rm followed by the container name or ID.

help me remove a Docker image that says it is still in use

Secret mission

Add a Multi-Stage Build

Take your Dockerfile to the next level by implementing a multi-stage build. This is how production Docker images are built in real companies.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • Docker containers (my-api, hello-world).
  • Docker images (devops-api:1.0, devops-api:1.1, hello-world, python:3.13-slim).
  • Project folder on your Desktop (devops-api directory with app.py, Dockerfile, requirements.txt, .dockerignore).
  • Python virtual environment (venv/ inside your project folder).
  • Docker Desktop application.

Keep everything running

No action needed. Choose this if you are still actively building or plan to continue learning Docker right away.

  • Docker Desktop stays running in your menu bar. It uses some memory and CPU while idle.
  • Your containers, images, and project files remain on disk ready for use.

Pause - I'll come back to this later

Shut down running processes to free up memory, but keep all your files and images so you can pick up where you left off.

  • Stop any running containers by running:
  • Quit Docker Desktop by clicking the whale icon in your menu bar and selecting Quit Docker Desktop.
docker stop my-api

When you come back, relaunch Docker Desktop and start your container again with:

docker start my-api

Delete - I don't want to use this again

Remove all project resources and start fresh. This removes containers, images, and project files from your machine.

  • Stop and remove all project containers by running:
docker stop my-api
docker rm my-api
docker rm $(docker ps -aq --filter ancestor=hello-world)
  • Remove all project images by running:
docker rmi devops-api:1.0 devops-api:1.1
docker rmi hello-world
docker rmi python:3.13-slim
  • Delete the project folder from your Desktop by running:
rm -rf ~/Desktop/devops-api
  • Uninstall Docker Desktop by opening Docker Desktop, clicking the Troubleshoot icon in the top-right corner, selecting Uninstall, and confirming by selecting Uninstall again.
  • Move /Applications/Docker.app to the Trash and empty the Trash.
  • Remove leftover Docker files by running:
rm -rf ~/Library/Group\ Containers/group.com.docker
rm -rf ~/.docker

Nice Work!

Nice Work!

You just containerized your first application! You now understand the Docker fundamentals that underpin every modern deployment pipeline.

You've learned how to:

  • Built a Python Flask REST API with three endpoints (/health, /info, /api/status).
  • Wrote a production-style Dockerfile with layer caching and a .dockerignore file to optimize builds.
  • Managed the full container lifecycle: building, running, inspecting, stopping, and removing containers with port mapping.
  • Secret Mission: Implemented a multi-stage build to optimize your Docker image size.

Ready to quiz yourself?