"It works on my machine" is one of the most common sources of deployment pain. Containers solve this by packaging an application with everything it needs to run, identically, anywhere.
What a container actually is
A container bundles your application code together with its exact dependencies, runtime, and configuration into one portable unit - the same container runs identically on your laptop, a teammate's machine, or a production server, because it carries its entire environment with it.
A basic Dockerfile
# Dockerfile
FROM node:20
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Each line builds a layer: start from a base Node.js image, copy in dependency info and install it, copy the rest of the code, then define how to start the app.
Building and running a container
docker build -t my-app .
docker run -p 3000:3000 my-app
docker build creates an image from your Dockerfile; docker run starts a container from that image, mapping port 3000 inside the container to port 3000 on your machine.
Images vs containers
An image is the packaged blueprint (built once); a container is a running instance of that image - the same relationship as a class and an object in object-oriented programming. You can run multiple containers from the same image simultaneously.
Why this matters for deployment
Once your app is containerized, deploying it to any server that runs Docker is consistent and predictable - no more "the server has a different Node version than my laptop." Most modern hosting platforms are built around running containers directly.