Docker solves a simple but painful problem: an application should run the same way on every machine. Instead of relying on whatever is installed on a laptop or server, Docker packages the application with the system libraries and runtime it needs.
This guide explains the core ideas without assuming previous container experience. You will build an image, run a container, map ports, use volumes, and learn the commands that make Docker useful in daily development.
Image vs container
An image is a template. A container is a running instance of that template.
Think of an image like a packaged application and a container like a process started from that package. You can start many containers from the same image, remove them, and start them again.
docker pull nginx:alpine
docker run --name web -p 8080:80 nginx:alpinedocker pull nginx:alpine
docker run --name web -p 8080:80 nginx:alpineOpen http://localhost:8080 and you should see Nginx. Stop and remove the container:
docker stop web
docker rm webdocker stop web
docker rm webThe image still exists locally. Only the running container was removed.
Your first Dockerfile
A Dockerfile describes how to build an image. For a small Node.js app it can look like this:
FROM node:22-alpine
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
EXPOSE 3000
CMD ["pnpm", "start"]FROM node:22-alpine
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
COPY . .
EXPOSE 3000
CMD ["pnpm", "start"]Build it:
docker build -t my-app .docker build -t my-app .Run it:
docker run --name my-app -p 3000:3000 my-appdocker run --name my-app -p 3000:3000 my-appThe left side of -p 3000:3000 is the port on your machine. The right side is the port inside the container.
Why layers matter
Docker builds images in layers. If package.json and the lockfile do not change, Docker can reuse the dependency installation layer and rebuild faster.
That is why the Dockerfile copies dependency files before copying the whole source tree. A small ordering change can make local builds much slower.
Environment variables
Pass configuration with environment variables instead of hardcoding it into the image.
docker run --rm -e NODE_ENV=production my-appdocker run --rm -e NODE_ENV=production my-appFor multiple variables, use an env file:
docker run --rm --env-file .env.local my-appdocker run --rm --env-file .env.local my-appDo not bake real secrets into the image. Images can be copied, cached, uploaded and inspected.
Volumes and persistent data
Containers are disposable. If a database writes data inside the container filesystem, that data disappears when the container is removed. Volumes solve this.
docker volume create postgres-data
docker run --name db -e POSTGRES_PASSWORD=secret -v postgres-data:/var/lib/postgresql/data postgres:18docker volume create postgres-data
docker run --name db -e POSTGRES_PASSWORD=secret -v postgres-data:/var/lib/postgresql/data postgres:18The container can be removed, but the named volume remains until you remove it explicitly.
Inspect what is happening
These commands are the everyday toolbox:
docker ps # running containers
docker ps -a # all containers
docker images # local images
docker logs -f my-app # follow logs
docker exec -it my-app sh # shell inside a container
docker inspect my-app # detailed metadatadocker ps # running containers
docker ps -a # all containers
docker images # local images
docker logs -f my-app # follow logs
docker exec -it my-app sh # shell inside a container
docker inspect my-app # detailed metadataWhen something fails, start with logs, then inspect environment variables, ports, and mounted volumes.
Clean up safely
During learning, Docker objects accumulate quickly. Remove stopped containers and unused images when needed:
docker container prune
docker image prune
docker volume lsdocker container prune
docker image prune
docker volume lsBe careful with volumes. Removing a volume can remove a local database that you still need.
Common beginner mistakes
The first mistake is confusing the host with the container. A port must be published with -p before it is reachable from your machine.
The second mistake is using localhost inside a container to reach another container. In multi-container setups, use Docker networks or Docker Compose service names.
The third mistake is treating containers like virtual machines. A container should run one main process, write persistent data to volumes, and be safe to recreate.
Docker becomes much easier once these ideas click: build images, run containers, publish ports, mount volumes, and keep configuration outside the image.