Creating a Docker container comes down to three steps: write a Dockerfile, build it into an image, and run that image as a container. This guide walks through each step with real commands you can copy, plus the container management commands you'll actually use day to day.
If you've never touched Docker before, or you've run a docker run command from a tutorial without really understanding what it did, this is the guide that fills in the gaps.
What You Need Before You Start
- Docker installed on your machine (Docker Desktop for Mac/Windows, or Docker Engine on Linux)
- A terminal
- A small application to containerize (this guide uses a minimal Node.js app, but the same steps apply to Python, Go, Java, or anything else)
Confirm Docker is installed and running:
docker --version
docker ps
If docker ps returns an empty table instead of an error, you're ready.
Step 1: Write a Dockerfile
A Dockerfile is a text file with no extension, named exactly Dockerfile, placed at the root of your project. It's a list of instructions Docker follows, in order, to build an image.
Here's a complete Dockerfile for a Node.js app:
FROM node:24-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "index.js"]
What each instruction does
FROM node:24-alpine — Sets the base image. Everything in this image (Node.js, npm, a minimal Linux filesystem) is now the foundation your container is built on. Alpine is used here because it keeps the final image small — see the base images guide linked at the end for why that choice matters.
WORKDIR /app — Creates /app inside the container and sets it as the working directory. Every instruction after this runs relative to /app.
COPY package*.json ./ — Copies package.json and package-lock.json into the container, before the rest of the code. This ordering matters — see the caching note below.
RUN npm install — Installs dependencies. This runs once, at build time, not every time the container starts.
COPY . . — Copies everything else in your project into /app.
EXPOSE 3000 — Documents that the app listens on port 3000. This is metadata for humans and tooling; it doesn't actually publish the port (that happens in Step 3).
CMD ["node", "index.js"] — The command that runs when a container starts from this image.
Why copy
package*.jsonbefore the rest of the code? Docker caches each instruction as a layer. If only your application code changes butpackage.jsondoesn't, Docker reuses the cachednpm installlayer instead of reinstalling every dependency on every build. Skip this ordering and every code change triggers a full reinstall.
Step 2: Build the Image
From the directory containing your Dockerfile, run:
docker build -t my-node-app .
-t my-node-app— Tags the image with a name so you can reference it later instead of a long hash.— The build context; tells Docker to look for the Dockerfile in the current directory
Docker pulls the base image (if it's not already cached locally) and executes each instruction in order. You'll see one line of output per instruction as it builds.
Confirm the image exists:
docker images
You should see my-node-app in the list, along with its size and image ID.
Step 3: Create and Run the Container
An image is a template; a container is a running instance of that template. This is the step that actually creates a Docker container from the image you just built:
docker run -d -p 3000:3000 --name my-running-app my-node-app
-d— Detached mode; runs in the background instead of tying up your terminal-p 3000:3000— Maps port 3000 on your machine to port 3000 inside the container (host:container) — this is what actually makes the app reachable--name my-running-app— Gives the container a name you can reference instead of its auto-generated ID
Visit localhost:3000 in your browser. That's a container, created and running from an image you built yourself.
Running without detached mode
If you want to see logs stream live in your terminal instead, drop -d:
docker run -p 3000:3000 --name my-running-app my-node-app
Press Ctrl+C to stop it.
Step 4: Manage the Container
Once a container exists, here's the command set you'll use constantly:
# List running containers
docker ps
# List all containers, including stopped ones
docker ps -a
# View logs
docker logs my-running-app
# Follow logs live
docker logs -f my-running-app
# Stop a running container
docker stop my-running-app
# Start a stopped container
docker start my-running-app
# Restart
docker restart my-running-app
# Open a shell inside a running container
docker exec -it my-running-app sh
# Remove a stopped container
docker rm my-running-app
# Remove a running container (force)
docker rm -f my-running-app
Common mistake: running
docker rmon a container that's still running fails with a "container is running" error. Eitherdocker stopit first, or usedocker rm -fto force it.
Rebuilding After a Code Change
Editing your source code doesn't update a running container — images are immutable snapshots. After a change, you rebuild and replace:
docker build -t my-node-app .
docker stop my-running-app
docker rm my-running-app
docker run -d -p 3000:3000 --name my-running-app my-node-app
During development, most people skip this cycle entirely and either use docker compose up --build or mount the source directory as a volume with -v $(pwd):/app so changes reflect without a rebuild. That's a topic for another guide — for now, know that the rebuild-stop-rm-run cycle above is what's actually happening under the hood.
Troubleshooting Common Errors
docker: command not found — Docker isn't installed, or Docker Desktop isn't running.
Cannot connect to the Docker daemon — Docker Desktop (or the Docker service on Linux) isn't running. Start it and retry.
port is already allocated — Something else is already using that host port. Either stop whatever's using it, or map to a different host port: -p 3001:3000.
No such container — You're referencing a container name or ID that doesn't exist, or was already removed. Check docker ps -a for the actual name.
Container exits immediately after docker run — Check docker logs <container-name>. This is almost always the application crashing on startup, not a Docker problem.
Why Use Docker at All?
- Consistency — the same environment in development, staging, and production
- Isolation — each container runs independently, without polluting your host machine or conflicting with other containers
- Portability — runs identically on any machine with Docker installed
- Efficiency — far lighter than a full virtual machine, since containers share the host kernel
Next Steps
You now know how to create, build, run, and manage a Docker container from scratch. From here, the natural next steps in this series are:
- Compressed vs. uncompressed image sizes — what those two numbers in
docker imagesactually mean - Choosing the right base image — Alpine vs. slim vs. distroless vs. scratch
- Dockerfile labels — metadata and organization for your images
- Layers, caching, and multi-stage builds — the techniques that take an image from 1GB to 50MB
Tags: Docker · how to create a docker container · container creation · Docker image building · containerization guide · Docker best practices · Docker for beginners · container deployment · Docker commands · run docker container