What Is Docker? A Beginner's Guide to Docker and Containers for Developers

What is Docker and what does it give developers?
Docker is an open source platform that packages an application with its code, runtime, libraries and settings into an isolated unit called a container. That container behaves the same on your laptop, on a test server and in production. As a result, the classic "it works on my machine" problem mostly disappears.
I have worked on web projects since 2012. The people who ask me "what is Docker" most often are teams that lose two days every time a new developer joins. In this guide I explain containers, Dockerfiles, the image versus container distinction, Docker Compose and the difference from virtual machines. I also keep the examples close to patterns I actually use.
For the official definition, read the Docker overview in the documentation. However, the docs are huge. So here I build the mental model first and only then move to commands.
What does the word container actually mean?
A container is an isolated workspace that shares the host kernel but has its own file system, network interface and process tree. On Linux, two kernel features make this possible: namespaces and cgroups. Namespaces limit what a process can see. Cgroups limit how much CPU and memory it can use.
In other words, a container is not a magic box. It is an ordinary Linux process with fences around it. That matters, because the app inside does not boot a separate operating system. Therefore it starts in seconds and uses few resources.
The idea is older than Docker. Chroot, FreeBSD jails and LXC existed for years. Docker made the technology easy to package, share and run with one command. Moreover, today the Open Container Initiative (OCI) defines the image format. That means an image you build with Docker also runs with other tools such as Podman.
What is the difference between Docker and a virtual machine?
A virtual machine runs a full operating system with its own kernel on top of a hypervisor that emulates hardware. A container shares the host kernel and carries only the files the app needs. You feel this difference in size, startup time and isolation.
| Criterion | Container (Docker) | Virtual machine (VM) |
|---|---|---|
| Kernel | Uses the host kernel | Each VM runs its own kernel |
| Startup time | Usually seconds | Usually minutes |
| Image size | Tens or hundreds of MB | Often several GB |
| Isolation | Process level, thinner | Hardware level, stronger |
| Typical use | Microservices, CI, dev environments | Other operating systems, strong isolation |
The times and sizes in the table are general tendencies, not measurements. They depend on what you put in the image. On the other hand, the two technologies are not rivals. In practice, cloud providers often run your Docker containers inside a virtual machine. In short, VMs split infrastructure and containers package applications.
How does Docker work under the hood?
Docker uses a client and server architecture. The docker command you type is the client. The Docker daemon (dockerd) does the real work in the background, and the client talks to it through a REST API.
- Docker client: takes commands such as docker run and docker build and forwards them.
- Docker daemon: builds images, starts containers and manages networks and volumes.
- containerd and runc: the layers below the daemon that manage the container lifecycle and start the process.
- Registry: the store for images; Docker Hub is the best known, but you can host a private one.
Mac and Windows work a little differently. Their kernels are not Linux, so Docker Desktop starts a lightweight Linux VM and runs your containers inside it. For that reason, bind mounted folders can feel slower on a Mac than on Linux. I notice this on my own Apple silicon laptop too.
What is the difference between an image and a container?
An image is a read only template. A container is a running instance started from that template. Think of a class and an object in programming. You can start dozens of containers from a single image at the same time.
An image consists of layers. Each instruction in a Dockerfile creates a new layer, and Docker caches those layers. For example, the base OS comes first, then dependencies and finally your code. When you change your code, only the last layer rebuilds. So your builds get faster.
When a container starts, Docker adds a thin writable layer on top of the image. If you remove the container, that layer goes too. Therefore data that must survive, such as a database file, belongs on a volume. This is the most common beginner surprise: you delete and restart a container and your data is gone.
You version images with tags, for example nginx:1.27 or myapp:v3. If you omit the tag, Docker assumes latest. That said, latest does not guarantee "newest". It is simply the default tag name. In production I recommend pinned version tags.
How do you install Docker and run your first container?
On Mac and Windows the easiest route is Docker Desktop. On Linux you can install Docker Engine from your distribution's repository. Because the steps change often, follow the official installation page.
After installation, work through these steps:
- Run docker version and confirm that both client and daemon respond.
- Run docker run hello-world to pull and start the test image.
- Start Nginx with docker run -d -p 8080:80 nginx and open localhost:8080 in your browser.
- List running containers with docker ps.
- Stop and remove the container with docker stop and then docker rm.
The -p 8080:80 flag maps port 8080 on the host to port 80 in the container. The -d flag runs the container in the background. Once these two flags click, you understand half of everyday Docker use.
What is a Dockerfile and how do you write one?
A Dockerfile is a plain text file that describes step by step how to build an image. A typical Dockerfile for a small Node.js app looks like this:
- FROM node:20-alpine
- WORKDIR /app
- COPY package*.json ./
- RUN npm ci --omit=dev
- COPY . .
- EXPOSE 3000
- CMD ["node", "server.js"]
FROM picks the base image. Many teams choose alpine variants because they are small. WORKDIR sets the working directory. We copy only the package files first for one reason: caching. If your dependency list has not changed, Docker skips reinstalling it even when your code changes.
Then you build the image with docker build -t myapp:1.0 . and the final dot tells Docker to use the current folder as build context. Also add a .dockerignore file that excludes node_modules, .git and .env. Your image gets smaller, and secrets stay out of it.
Which Dockerfile mistakes should you avoid?
The mistakes I see in my own projects and in inherited code look very similar. Avoiding these makes images smaller and safer:
- Baking secrets into the image: an API key in an ENV line stays in the image history forever. Pass it at runtime instead.
- Running as root: switch to an unprivileged user with the USER instruction.
- Oversized base images: try slim or alpine variants instead of full Ubuntu.
- Cache breaking order: copy frequently changing code last.
- Floating versions: use a specific tag in FROM instead of latest.
Then there is the multi stage build. In the first stage you compile the app with the full toolchain. In the second stage you copy only the output into a small base image. For Go, Java and front end projects this cuts image size noticeably. I now use it by default whenever a project has a build step.
What are volumes and bind mounts for?
The writable layer of a container is temporary. Consequently, you need to keep persistent data outside it. Docker offers two main options: volumes and bind mounts.
- Volume: storage that Docker manages. Create one with docker volume create data and attach it with -v data:/var/lib/mysql. This is the first choice for databases.
- Bind mount: maps a specific host folder into the container. It is handy in development because code changes appear instantly.
- tmpfs: keeps data in memory only; it disappears when the container stops.
My rule of thumb is simple: volumes in production, bind mounts in local development. Volumes are also easy to back up, because you can start a temporary container and archive the volume. If you run a database in a container, set up backups on day one, not later.
How does Docker networking work?
Each container joins a virtual network called bridge by default. Containers on the same user defined network can reach each other by name. So your app connects to the database as db instead of an IP address. Docker handles that name resolution with its internal DNS.
Create a network with docker network create app-net and attach containers with the --network flag. For services you want to expose, you must publish ports with -p. A port you do not publish stays reachable only inside the network. That is exactly what you want for a database.
One security note matters here. On Linux, Docker edits iptables rules itself when it publishes ports. This can bypass simple firewall rules such as ufw. Therefore publishing a database with -p 3306:3306 on a server may expose it to the internet. If you only need local access, write -p 127.0.0.1:3306:3306 instead.
What is Docker Compose and when should you use it?
Docker Compose is a tool that lets you define a multi container application in one YAML file and start it with one command. For example, a web app, a database and a cache. Instead of three separate docker run commands, you describe all of them in compose.yaml.
A typical file has a services key with two entries, web and db. The web service builds from its own Dockerfile with build: . and publishes port 3000. The db service uses the postgres:16 image, attaches a volume and reads its password from an environment variable. Finally, depends_on tells Compose to start web after db.
Next you start everything with docker compose up -d, follow logs with docker compose logs -f and shut down with docker compose down. There used to be a separate docker-compose binary. Today Compose ships inside Docker as the docker compose subcommand. The Compose documentation is a solid reference.
Which Docker commands will you use every day?
In your first weeks, the commands below cover most of the work. I suggest keeping them somewhere handy:
| Command | What it does |
|---|---|
| docker ps -a | Lists running and stopped containers |
| docker images | Shows local images |
| docker logs -f name | Follows container logs live |
| docker exec -it name sh | Opens a shell inside a running container |
| docker build -t name:tag . | Builds an image from a Dockerfile |
| docker pull / push | Downloads or uploads images to a registry |
| docker system df | Summarises disk usage |
| docker system prune | Cleans up unused data |
Above all, docker exec and docker logs are the basis of debugging. Still, use docker system prune with care. Depending on flags, it removes stopped containers and unused images. When a disk fills up, docker system df is the first thing I check.
Where is Docker genuinely useful?
Docker does not fix everything. In some scenarios, however, the difference is obvious. Here are the use cases that stand out in my experience:
- Fast onboarding: a new developer clones the repo, runs docker compose up and has a working environment within minutes.
- Running versions side by side: one project needs PHP 7.4 and another needs PHP 8.3. Both can live on one server without conflict.
- Continuous integration: tests run in a clean and identical environment every time.
- Keeping legacy apps alive: an old system that you cannot upgrade runs in its own container while the host stays modern.
- Microservices: each service ships with its own dependencies.
I lived the legacy case myself. We moved a system that required an old PHP version into a separate container. As a result, the rest of the server stayed up to date. In web projects this approach also simplifies go live during web design work.
Which languages and frameworks work with Docker?
If you ask "what is Docker" from a language angle, the answer is short: it is language agnostic. Node.js, Python, PHP, Java, Go, .NET or Ruby, it does not matter. If your app runs on Linux, you can containerise it. Docker Hub offers official base images for most of these languages.
Each language still has its own tricks. For example, in Python projects copying the requirements file separately protects the cache. In PHP, splitting the web server and PHP-FPM into separate containers is a common pattern. For compiled languages such as Java and Go, a multi stage build shrinks the image a lot.
Front end projects differ slightly. You build your React or Vue app in one stage and copy the static output into a small Nginx image. Then production does not even need Node.js. Moreover, this approach puts your server configuration under version control too.
So for a team wondering what is Docker in practice, the first step is simple. Pick the official image of your main language and run one small experiment. That experiment becomes the template for later projects.
What is a registry and how do you share images?
A registry stores and distributes images by tag. Docker Hub is the best known. GitHub Container Registry, GitLab and the cloud providers offer their own. In other words, your code lives in Git and your image lives in a registry.
To share an image, first tag it with a name that includes the registry address. Then log in with docker login and upload with docker push. On the server, docker pull fetches the same image. So you build once and ship the same package to every environment.
I have two recommendations here. First, never push internal images to a public repository; use a private one. Second, prefer images marked as official or from verified publishers on Docker Hub. An image from an unknown author means unknown code on your server.
Good tagging habits also make rollbacks easy. If a release misbehaves, returning to the previous tag takes minutes.
How do you keep Docker images secure?
Image security comes from small habits rather than one setting. This is the checklist I follow in my projects:
- Update the base image regularly and rebuild images on a schedule.
- Scan for known vulnerabilities with a tool such as Docker Scout or Trivy.
- Run containers as a non root user.
- Publish only the ports you really need.
- Pass passwords and keys at runtime, never inside the image.
Also note that mounting the Docker socket (/var/run/docker.sock) into a container effectively gives it root on the host. Some monitoring tools ask for this, so make sure you trust the source. The Docker Engine security page is a good starting point.
Meanwhile, your site's wider setup matters as much as containers. For example, my guide on business email on a custom domain covers domain level settings.
Where should you start when debugging Docker?
When a container misbehaves, a calm and ordered approach solves most issues quickly. This is the sequence I follow:
- Check status and exit code with docker ps -a.
- Read the latest application output with docker logs.
- Inspect environment variables, networks and mounted volumes with docker inspect.
- Step inside with docker exec -it and confirm files and ports are where you expect.
The three most common mistakes are predictable. First, the app listens on 127.0.0.1 instead of 0.0.0.0. Second, the port mapping is wrong. Third, an environment variable is missing. The first one confuses beginners most. The app runs inside the container, yet you cannot reach it from outside, because 127.0.0.1 only means the container itself.
In practice, logs, inspect output and a look inside are usually enough. If the problem persists, start the same image locally and compare step by step.
What do people get wrong when they ask what is Docker?
When someone asks what is Docker, the most common wrong answer is "a lightweight virtual machine". The analogy helps at first, but it also leads to bad decisions. Installing an SSH server in a container, stuffing several services into one container or patching containers by hand all come from that wrong model.
Instead, treat a container as a single purpose process you can delete and recreate at will. If something needs to change, you do not log in and fix it. You fix the Dockerfile and build a new image. This discipline is also the foundation of infrastructure as code.
Also, Docker is not a firewall. Container isolation is thinner than a VM because containers share the kernel. For that reason, do not rely on containers alone to run untrusted code. Layer your defences with non root users and fresh images.
Is Docker Desktop free, and what about licensing?
Docker Engine is open source and free on Linux servers. Docker Desktop is a separate product, and larger companies need a paid subscription for commercial use. According to Docker's pricing page, organisations with more than 250 employees or more than 10 million US dollars in annual revenue need a paid plan.
The Personal plan stays free for individual developers, education and small businesses. Still, terms can change, so check the current page before rolling it out across a company. Alternatively, some teams use Colima or OrbStack on Mac, or Docker Engine and Podman directly on Linux.
How do you run Docker in production, and do you need Kubernetes?
For small and mid sized projects, running production on a single server with Docker Compose is entirely viable. Many of my own projects work this way: one Compose file, Nginx or Caddy as a reverse proxy in front, regular backups and monitoring. Kubernetes makes sense for systems that span many servers and must scale automatically.
So "everyone uses it" is not a good reason to adopt Kubernetes. It brings a real operational load. First, answer these questions honestly:
- Does one server handle your traffic and your downtime tolerance?
- Does the team have the knowledge and time to run a cluster?
- Do you really need autoscaling, or would a bigger server do?
If your answers mostly point to "one server is enough", start with Compose. When scale truly grows, your images are already ready, so the move becomes easier.
Does Docker affect website speed and SEO?
Docker is not a ranking factor. Google neither knows nor cares whether your site runs in a container. However, there are indirect effects. A consistent environment reduces deployment errors and downtime, and downtime hurts both crawling and user experience.
On the other hand, a badly configured container can slow server response time. For instance, a container with too little memory can slow page generation. To measure speed, see my guide on the Google Lighthouse performance test. For how speed affects rankings, read how site speed affects SEO.
If you move to Docker during a migration, check redirects with the redirect checker. The website migration SEO checklist covers the search side of the move.
What learning path should you follow for Docker?
To move from "what is Docker" to real use, I suggest this order. Practise each step with a small project, because reading alone is not enough.
- Refresh basic Linux commands and the ideas of processes, ports and file permissions.
- Try docker run, ps, logs and exec with ready made images.
- Write a Dockerfile for a small app of your own and add .dockerignore.
- Test volumes and networks together with a database.
- Combine app and database in one Docker Compose file.
- Shrink and harden the image with a multi stage build and a non root user.
- Build the image automatically in a CI pipeline and push it to a registry.
Steady practice can cover these seven steps in a few weeks. That estimate varies by person; it is field experience, not a guarantee. Once you move into enterprise topics such as micro frontend architecture, container skills become a baseline.
How would you sum up what is Docker in a few lines?
In short, Docker turns your application and its dependencies into a portable package and runs that package the same way everywhere. The image is the template and the container is the running instance. The Dockerfile is the recipe, and Compose conducts several services together.
My advice is not to move everything into Docker on day one. Start with your local development environment, see the benefit, then extend to CI and production. That way the team learns the tool while the work keeps moving.
If you want a roadmap for infrastructure, speed or migration on your web project, write to me through the contact page. You can find more posts in the software category.




