Sign in Get started
Solutions
ComplianceDigital ForensicsAccount SecurityWebsite SecurityWeb OptimizationSEO / GEOBusiness IntelligenceUI / UXStartupsDigital TransformationWhite LabelAI Code AuditAI / MLConsulting & AdvisoryBusiness Strategy
Products
Web HostingVPS HostingSSL CertificatesSiteLock SecurityWebsite BackupEmail Spam FilterOX App Suite360 MonitoringWeebly BuilderSite BuilderSEO (Marketgoo)Account SecurityDomainsAll Products
More
NetworkSecurity AboutCareersContactRoadmapLegal
Guide

Docker for developers: a practical start

Docker packages an application with everything it needs to run: the language runtime, the libraries and the start command. The same package runs the same way on your laptop, a colleague's laptop and a server. That removes a whole class of "it works on my machine" problems.

This guide covers the parts you use every week: images and containers, a Dockerfile for a Python app and one for a front-end build, Docker Compose, volumes, a daily workflow and the security basics.

Images and containers

An image is a read-only template. It is built in layers, one per instruction in a Dockerfile, and Docker reuses a layer when nothing in it has changed. A container is a running instance of an image, with a thin writable layer on top. You can start ten containers from one image, and removing a container leaves the image untouched.

From a Dockerfile to a running containerDockerfileA recipe, kept inyour repositorydocker buildRuns the recipestep by stepImageRead-only layers,stored and shareddocker runStarts a containerfrom the imageDockerfileA recipe, kept in your repositorydocker buildRuns the recipe step by stepImageRead-only layers, stored and shareddocker runStarts a container from the image
One image can start many containers. Each container gets its own writable layer on top.

A useful way to think of it: the image is the class, the container is the object.

A Dockerfile for a Python app

This file builds an image for a Django or Flask app served by Gunicorn. The order of the steps matters. Dependencies change less often than code, so copying requirements.txt and installing it first lets Docker reuse that layer on most rebuilds.

# Dockerfile for a Python web app
FROM python:3.14-slim

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

WORKDIR /app

# Dependencies first, so this layer is cached
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Then the code
COPY . .

# Run as an ordinary user, not root
RUN useradd --create-home appuser
USER appuser

EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "mysite.wsgi"]

The python:3.14-slim base is a small Debian image with Python 3.14. Python 3.15 was released on 9 October 2026, so check that your libraries support it before you move. The Python versions page lists every branch and its end of life.

Add a .dockerignore next to the Dockerfile. It keeps secrets, local environments and build output out of the image, and makes builds faster:

# .dockerignore
.git
.venv
__pycache__
node_modules
dist
.env

Build and run it:

docker build -t myapp:dev .
docker run --rm -p 8000:8000 myapp:dev

-p 8000:8000 maps a port on your machine to the port inside the container, and --rm removes the container when it stops.

A Dockerfile for a front-end build

A React, Vue or Svelte app needs Node.js to build, but the result is just static files. A multi-stage build uses Node in the first stage and copies only the finished files into a small web server image. Node, your packages and your source code never reach the final image.

# Dockerfile for a front-end build (two stages)
FROM node:24-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginxinc/nginx-unprivileged:stable-alpine
COPY --from=build /app/dist /usr/share/nginx/html
EXPOSE 8080

Node 24 and Node 26 are both long-term support lines according to the Node.js release schedule. The nginx-unprivileged image runs nginx as an ordinary user and listens on port 8080. If your app uses client-side routing, add an nginx rule that sends unknown paths to index.html, as described in Deploying a React or Django app.

Docker Compose

Most apps need more than one container: the app, a database, maybe a cache. Compose describes them in one file, compose.yaml, and starts them with one command. Compose v2 is part of the Docker CLI, so the command is docker compose with a space. The old docker-compose tool and the top-level version: key are no longer needed; Docker's reference calls that key obsolete.

# compose.yaml
services:
  web:
    build: .
    ports:
      - "8000:8000"
    env_file: .env
    depends_on:
      - db
    develop:
      watch:
        - action: sync
          path: .
          target: /app
          ignore:
            - .venv/
        - action: rebuild
          path: requirements.txt

  db:
    image: postgres:17
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - dbdata:/var/lib/postgresql/data

volumes:
  dbdata:

Services on the same Compose project reach each other by service name, so the app connects to the database at host db. Keep the real password in a .env file that is listed in .gitignore.

Volumes

Anything written inside a container's own layer disappears when the container is removed. For data that must survive, use a volume.

Three ways a container stores dataContainer layerGone when the containeris removed. Scratch only.Named volumeManaged by Docker.Use it for databases.Bind mountA folder from yourmachine. Use it in dev.Container layerGone when the containeris removed. Scratch only.Named volumeManaged by Docker.Use it for databases.Bind mountA folder from yourmachine. Use it in dev.
Pick the storage by how long the data has to live.

In the Compose file above, dbdata is a named volume, so the database keeps its rows across docker compose down and up. To delete it on purpose, run docker compose down -v. Bind mounts map a folder on your machine into the container. They are handy in development and best avoided in production, where the image should contain the code it runs.

A daily workflow

The develop.watch section in the Compose file tells Compose what to do when files change: sync copies edited code into the running container, and rebuild builds a new image when the dependency list changes. With a framework that reloads on its own, such as Django's development server, you see changes without restarting anything.

docker compose up --watch     # start everything, sync code changes
docker compose logs -f web    # follow one service's output
docker compose exec web sh    # open a shell inside it
docker compose down           # stop and remove the containers

A few habits help:

  • Run tests inside the container, with docker compose exec web pytest, so they use the same versions as production.
  • Pin base images to a version (python:3.14-slim, postgres:17) and update them on purpose. latest changes under you.
  • Clear old images now and then with docker image prune.

Security basics

  • Run as a normal user. Add a USER line, as in the Python Dockerfile, or use an image that already does it, such as nginx-unprivileged. If someone breaks into the app, they do not get root inside the container.
  • Use small base images. Slim and Alpine images contain fewer packages, so there is less to patch and less to attack.
  • Keep secrets out of the image. Pass them at run time with environment variables or Compose secrets. Anything copied in with COPY can be read by anyone who has the image.
  • Rebuild regularly. Base images get security fixes. A rebuild picks them up; an old image keeps its old holes.
  • Scan your images. docker scout cves myapp:dev lists known vulnerabilities in the packages inside an image.
  • Publish only the ports you need. A database rarely needs a port open to the outside world.

The Docker build best practices page goes deeper on each of these.

Common questions

Is a container a virtual machine?
No. A container shares the host's kernel and isolates processes, files and network. It starts in about a second and uses far less memory than a virtual machine.
Should I use Alpine for Python?
Usually the slim Debian image is the easier choice. Many Python packages ship ready-built wheels for Debian-based systems, while on Alpine some have to compile from source.
Do I need Docker in production?
Not always. A small app runs fine on a plain server. Docker pays off when you deploy often, run several services or want the same setup everywhere.
Why does my rebuild take so long?
A step near the top of the Dockerfile changed, so every layer after it was rebuilt. Copy files that change often as late as possible.

If you build and ship containers for clients, Getting paid on Internetivo explains how professionals find paid tasks.