beginner ~45 min updated 2026-06-01
Docker Build and Push
Write a multi-stage Dockerfile for a small Python API, build a multi-architecture image with docker buildx, tag it correctly, and push it to Docker Hub with build cache enabled.
Objective
Build a multi-stage, multi-architecture Docker image with buildx and publish it to Docker Hub with semantic tags. You will learn why multi-stage builds shrink images, how layer caching works, and how one build can serve both amd64 and arm64 machines.
Prerequisites
- Docker Engine 24 or newer with the buildx plugin (
docker buildx versionworks) - A free Docker Hub account
- Basic terminal familiarity
- Python 3 installed locally (only needed to understand the sample app)
Architecture
A small Flask API is packaged with a two-stage Dockerfile: the builder stage installs dependencies into a virtualenv, and the slim runtime stage copies only that virtualenv plus the app code. Buildx uses a container driver to cross-compile for amd64 and arm64 and pushes a single multi-arch manifest list to Docker Hub.
Dockerfile (multi-stage)
+---------------------------+
| stage 1: builder |
| python:3.12 + pip install|
+------------+--------------+
| copy /opt/venv
+------------v--------------+ docker buildx build
| stage 2: runtime | ----> --platform amd64,arm64
| python:3.12-slim + app | |
+---------------------------+ v
docker.io/USER/flask-lab:1.0.0
(manifest list: amd64 + arm64)
Steps
1. Create the sample application
mkdir docker-lab && cd docker-lab
# app.py
from flask import Flask, jsonify
import platform
app = Flask(__name__)
@app.get("/")
def index():
return jsonify(status="ok", arch=platform.machine())
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
# requirements.txt
flask==3.0.3
2. Write the multi-stage Dockerfile
# Dockerfile
# ---- build stage ----
FROM python:3.12 AS builder
WORKDIR /app
COPY requirements.txt .
RUN python -m venv /opt/venv && \
/opt/venv/bin/pip install --no-cache-dir -r requirements.txt
# ---- runtime stage ----
FROM python:3.12-slim
WORKDIR /app
COPY --from=builder /opt/venv /opt/venv
COPY app.py .
ENV PATH="/opt/venv/bin:$PATH"
EXPOSE 5000
USER nobody
CMD ["python", "app.py"]
Add a .dockerignore:
# .dockerignore
__pycache__/
*.pyc
.git
3. Build and test locally
docker build -t flask-lab:dev .
docker run -d --name flask-lab -p 5000:5000 flask-lab:dev
curl -s http://localhost:5000
docker rm -f flask-lab
docker images flask-lab
4. Log in and create a buildx builder
docker login
docker buildx create --name lab-builder --driver docker-container --use
docker buildx inspect --bootstrap
5. Build multi-arch and push
Replace YOUR_USER with your Docker Hub username:
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag YOUR_USER/flask-lab:1.0.0 \
--tag YOUR_USER/flask-lab:latest \
--cache-to type=inline \
--push .
6. Verify the manifest list
docker buildx imagetools inspect YOUR_USER/flask-lab:1.0.0
docker pull YOUR_USER/flask-lab:1.0.0
docker run --rm -d -p 5000:5000 --name verify YOUR_USER/flask-lab:1.0.0
curl -s http://localhost:5000
docker rm -f verify
Expected output
$ curl -s http://localhost:5000
{"arch":"x86_64","status":"ok"}
$ docker images flask-lab
REPOSITORY TAG IMAGE ID SIZE
flask-lab dev 3f9a1b2c4d5e 142MB
$ docker buildx imagetools inspect YOUR_USER/flask-lab:1.0.0
Name: docker.io/YOUR_USER/flask-lab:1.0.0
MediaType: application/vnd.oci.image.index.v1+json
Manifests:
Platform: linux/amd64
Platform: linux/arm64
Troubleshooting
denied: requested access to the resource is deniedon push: you tagged the image without your Docker Hub namespace, ordocker loginexpired. Tag asYOUR_USER/flask-laband log in again.multiple platforms feature is currently not supported for docker driver: you are using the default docker driver. Create a container driver builder:docker buildx create --driver docker-container --use.- arm64 build is extremely slow: QEMU emulation is doing the work. That is normal for cross-builds; install emulators explicitly with
docker run --privileged --rm tonistiigi/binfmt --install allif the arm64 stage errors out. COPY failed: file not found: the file is excluded by.dockerignoreor you are building from the wrong context directory. Run the build from insidedocker-lab/and check the ignore file.- Port 5000 already in use: another process (on macOS often AirPlay Receiver) owns it. Map a different host port:
-p 5050:5000.
Cleanup
docker rm -f flask-lab verify 2>/dev/null || true
docker rmi flask-lab:dev YOUR_USER/flask-lab:1.0.0 YOUR_USER/flask-lab:latest 2>/dev/null || true
docker buildx rm lab-builder
docker logout
cd .. && rm -rf docker-lab
# Optionally delete the repository in the Docker Hub web UI.