Tutorials › Docker

Metals Simple Deploy · Docker

Packaging This Project into Containers

Both halves of this application — the Python API and the browser UI — ship as Docker images: self-contained bundles holding the code, the runtime that executes it, and every library it depends on. The same image you build on your laptop is the one Azure runs in production, which removes an entire category of “works on my machine” problems. This section starts with what an image actually is, then walks through the two Dockerfiles in this repository line by line.

Codey the Sr Developer, standing with a pointer, ready to walk through the guide
Images in this project → 3 API base → python:3.11-slim UI base → nginx-unprivileged:stable-alpine Local orchestration → docker-compose.yml Registry in Azure → Azure Container Registry

Why containers at all start here

Before containers, deploying this API meant telling a server: install Python 3.11, install these exact library versions, set these environment variables, run it with this command. Every one of those steps could drift — a server on Python 3.9, a library a minor version ahead, a forgotten variable. A container image freezes all of it into a single artifact, so “deploying” becomes “run this exact image” instead of “reproduce this environment correctly.”

Codey typing on a laptop
build once, run anywhere
Isn't that just a virtual machine?the usual question

A virtual machine carries an entire guest operating system — its own kernel, its own boot process, gigabytes of files — and that's why VMs take minutes to start. A container shares the host's kernel and packages only the layer above it: your code, your runtime, your libraries. That's why the API image here starts in about a second, and why it's measured in hundreds of megabytes rather than tens of gigabytes.

What it buys this project specificallythree concrete wins
  • One command to run everything. docker compose up starts the database, API, and UI together, wired to each other, with no local Python or PostgreSQL install required.
  • The UI gets a real web server. Instead of a development-only static file server, the UI image runs Nginx — the same server that will serve it in Azure.
  • Deployment becomes a file copy. Shipping a new version means pushing an image to a registry and telling Azure to pull it, covered in the Deployment section.

The three words worth learning first vocabulary

Nearly every Docker error message and command uses these three terms, and mixing them up is the single most common source of confusion when starting out.

TermWhat it means, in plain terms
imageA read-only template — the packaged filesystem and the command to run. Built once from a Dockerfile, never modified afterward. Think of it as a class definition, or an ISO file.
containerA running instance of an image. You can start many containers from one image; each gets its own writable layer, its own process, its own network address. Think of it as an object created from that class.
registryA server that stores and serves images, so other machines can pull them. Docker Hub is the public one; this project pushes its three images to a private Azure Container Registry.

The whole deployment story of this project is just those three nouns in order: build an image, push it to a registry, then have Azure pull it and start a container from it.

What this project actually containerizes three images

Three Dockerfiles, each producing one image: the two halves of the application, plus the tutorial site you're reading. The database is not one of them — locally it runs from the official postgres:16 image straight off Docker Hub with no customization, and in Azure it isn't a container at all but a managed PostgreSQL Flexible Server.

Codey pointing to the left
three Dockerfiles, one compose file
FileWhat it builds
metals_api/DockerfileThe metals-api image — Python 3.11 plus the Flask app, served by Gunicorn on port 5000. (dig deeper)
metals_ui/DockerfileThe metals-ui image — Nginx serving the static UI on port 8080 and proxying /api/ through to the API. (dig deeper)
tutorials/DockerfileThe metals-tutorials image — Nginx serving this tutorial site on port 8080. Independent of the application: no database, no API, no configuration.
docker-compose.ymlNot an image — the recipe that runs all three of the above plus a PostgreSQL container together on your machine. (dig deeper)
.dockerignoreTwo of them (repo root and metals_ui/) controlling which files are allowed into each build.

Pick a page to learn go deeper

Codey giving a thumbs up

New to containers? Start at page 1. Already comfortable and just want to see this project's files? Jump to the API image.