Skip to main content
In this lesson you’ll build a small custom Docker image for a Flask web application. The workflow is:
  1. Run the app interactively inside an Ubuntu container to enumerate the exact installation and runtime steps.
  2. Convert those verified steps into a Dockerfile.
  3. Build, inspect, and run the image from the host.
This approach ensures the image performs only the required actions and helps avoid surprises during image builds.

Goals

  • Verify the Flask app runs inside a container.
  • Capture the steps required to install dependencies and run the app.
  • Convert the manual steps to a reproducible Dockerfile and build the image.
  • Run the container and expose the application port to the host.

Overview (manual process)

  1. Start an Ubuntu container and open a shell.
  2. Update the apt package index and install Flask for Python 3.
  3. Create or copy the application source code inside the container.
  4. Set the FLASK_APP environment variable to point to the application file.
  5. Run the Flask development server binding to all interfaces (0.0.0.0).
  6. Verify the app is reachable from the host using a port mapping.

Manual run (verify the app works)

Start an interactive Ubuntu container:
Inside the container, update apt and install the Flask package for Python 3:
Create the application source file /opt/app.py (use cat or an editor):
Set the environment variable and run the server:
Sample server output inside the container (trimmed):
From the host (when you map ports) verify the endpoints:
When installing packages interactively you may encounter debconf prompts (timezone, etc.). When building images, set DEBIAN_FRONTEND=noninteractive in the Dockerfile to avoid interactive prompts. This lesson shows that approach.

Converting manual steps into a Dockerfile

Key considerations when writing the Dockerfile:
  • Combine apt-get update and apt-get install in a single RUN instruction to keep image layers smaller and avoid stale apt caches.
  • Use ENV DEBIAN_FRONTEND=noninteractive to prevent interactive package configuration prompts during build.
  • Use the exec form (["cmd", "arg"]) for ENTRYPOINT or CMD to ensure proper signal handling and avoid shell parsing issues.
Create a file named Dockerfile with the following contents:

What each Dockerfile line does

Build the Docker image

From the directory containing Dockerfile and app.py, build and tag the image webapp:
Trimmed example of build progress (informational):
Inspect local images:
Sample output (sizes will vary):

Run the container and expose the port

If you run the container without mapping ports, the app runs inside the container but is not reachable from the host. Map container port 5000 to host port 5000:
Then open the following in your browser: You should see the responses returned by the Flask app.

Useful commands (quick reference)

Callouts and best-practice notes

The Dockerfile shown reduces interactive prompts during build via DEBIAN_FRONTEND=noninteractive, and keeps image layers smaller by combining apt-get update and apt-get install in a single RUN and clearing apt lists. For production, avoid Flask’s built-in development server — use a production WSGI server like Gunicorn and consider an official Python or Alpine-based image to reduce final image size. See Flask docs and Gunicorn.
The Flask built-in server is for development only. Before using containers in production, replace the development server with a production-ready WSGI server, apply container security best practices, and minimize the image footprint.

Next steps

  • Replace Flask’s built-in server with a production WSGI server (e.g., Gunicorn) and update the Dockerfile ENTRYPOINT accordingly.
  • Use a smaller base image (for example, python:3-slim or an Alpine-based image) to reduce image size.
  • Add a .dockerignore file to keep unnecessary files out of the build context.
  • Review Docker best practices: Dockerfile reference and Docker documentation.

Watch Video