Skip to main content

Command Palette

Search for a command to run...

Docker Tutorial for Developers: From Basics to Full-Stack Deployment

Updated
•14 min read•View as Markdown
A

Hi there! I'm Aditya, a passionate Full-Stack Developer driven by a love for turning concepts into captivating digital experiences. With a blend of creativity and technical expertise, I specialize in crafting user-friendly websites and applications that leave a lasting impression. Let's connect and bring your digital vision to life!

Docker is a platform that allows developers to package applications and their dependencies into containers, making them easier to run across different environments.

Suppose you build a full-stack application using React, Node.js, and PostgreSQL. It works on your computer but fails on another machine because of different runtime versions, missing dependencies, or incorrect configuration.

Docker helps solve this problem by packaging the application environment into a reusable image.

In this guide, you will learn the Docker concepts and commands most useful for development, deployment, and technical interviews.

What Is Docker?

Docker is a platform for building, distributing, and running containerized applications.

A container packages an application's filesystem and runtime configuration while isolating its processes from other applications.

Docker is commonly used for:

  • Maintaining consistent development environments.

  • Deploying applications across different environments.

  • Running frontend, backend, and database services independently.

  • Automating builds and deployments through CI/CD.

  • Simplifying application setup and onboarding.

Docker Architecture

Docker uses a client-server architecture.

  • Docker CLI: Accepts commands such as docker build and docker run.

  • Docker daemon: Manages images, containers, networks, and volumes.

  • Docker image: A packaged template used to create containers.

  • Docker container: A running or stopped instance of an image.

  • Docker registry: Stores and distributes images, such as Docker Hub.

The basic workflow is:

Source Code
    |
    v
Dockerfile
    |
    v
Docker Image
    |
    v
Docker Container
    |
    v
Running Application

Dockerfile vs. Image vs. Container

Concept Meaning
Dockerfile Instructions for building an image
Image Packaged application environment
Container Instance created from an image
Registry Storage for distributing images
Volume Storage that can persist beyond a container's lifecycle
Network Enables communication between containers

Interview point: A Dockerfile builds an image, and an image is used to create containers.

Installing Docker

Install Docker Desktop on Windows or macOS, or Docker Engine on Linux.

Official installation guides:

Verify your installation:

docker --version
docker version
docker info
docker run hello-world

The hello-world command checks whether Docker can successfully run a test container.

Essential Docker Commands

Working with images

docker pull nginx:stable
docker images
docker build -t my-app:1.0 .
docker tag my-app:1.0 username/my-app:1.0
docker push username/my-app:1.0
docker rmi my-app:1.0
docker history my-app:1.0
  • pull: Downloads an image.

  • images: Lists local images.

  • build: Builds an image from a Dockerfile.

  • tag: Assigns another image reference.

  • push: Uploads an image to a registry.

  • rmi: Removes an image when it is no longer referenced by containers.

  • history: Displays image layers and build history.

Working with containers

docker run -d --name my-app -p 3000:3000 my-app:1.0
docker ps
docker ps -a
docker logs my-app
docker logs -f my-app
docker exec -it my-app sh
docker stop my-app
docker start my-app
docker restart my-app
docker rm my-app
docker inspect my-app
docker stats

Important distinctions:

  • docker run creates and starts a new container.

  • docker start starts an existing stopped container.

  • docker ps lists running containers.

  • docker ps -a also lists stopped containers.

  • docker exec runs a command inside a running container.

  • docker logs displays container output.

  • docker stop stops a container without deleting it.

Understanding port mapping

docker run -d -p 8080:80 nginx:stable

The syntax is:

-p HOST_PORT:CONTAINER_PORT

Here, port 8080 on the host forwards traffic to port 80 inside the container.

EXPOSE in a Dockerfile documents a container port; it does not publish that port to the host.

Writing a Dockerfile

A Dockerfile defines how Docker builds an application image.

Consider a Node.js application:

FROM node:22-bookworm-slim

WORKDIR /app

COPY package*.json ./

RUN npm ci

COPY . .

ENV NODE_ENV=production

EXPOSE 3000

CMD ["npm", "start"]

Important Dockerfile instructions

Instruction Purpose
FROM Selects the base image
WORKDIR Sets the working directory
COPY Copies files into the image
RUN Executes commands during image building
ENV Sets environment variables
ARG Defines build-time variables
EXPOSE Documents the intended container port
CMD Defines the default startup command
ENTRYPOINT Defines the primary executable
USER Selects the user for subsequent execution

CMD vs. ENTRYPOINT

CMD defines the default command or arguments for a container.

CMD ["node", "server.js"]

ENTRYPOINT defines the primary executable.

ENTRYPOINT ["node"]
CMD ["server.js"]

Together, these instructions run node server.js by default.

The exec form, which uses a JSON array, is generally preferred for application startup because it handles process signals more predictably.

RUN vs. CMD

RUN executes during image building.

RUN npm ci

CMD executes when the container starts.

CMD ["npm", "start"]

Building and Running Your First Docker Image

Create a Node.js project:

mkdir docker-demo
cd docker-demo
npm init -y
npm install express

Create server.js:

const express = require("express");

const app = express();
const PORT = process.env.PORT || 3000;

app.get("/", (req, res) => {
  res.json({ message: "Hello from Docker!" });
});

app.listen(PORT, "0.0.0.0", () => {
  console.log(`Server running on port ${PORT}`);
});

Create a Dockerfile:

FROM node:22-bookworm-slim

WORKDIR /app

COPY package*.json ./

RUN npm ci --omit=dev

COPY server.js ./

ENV PORT=3000

EXPOSE 3000

CMD ["node", "server.js"]

Create .dockerignore:

node_modules
.git
.env
npm-debug.log
coverage

Build the image:

docker build -t docker-demo:1.0 .

Run the container:

docker run -d --name docker-demo -p 3000:3000 docker-demo:1.0

Test the application:

curl http://localhost:3000

Expected response:

{"message":"Hello from Docker!"}

Inspect the logs:

docker logs docker-demo

Stop and remove the container:

docker stop docker-demo
docker rm docker-demo

Containerizing a Frontend Application

A React application built with Vite typically generates static files that can be served using Nginx.

Create a React application if you do not already have one:

npm create vite@latest frontend -- --template react
cd frontend
npm install

Create frontend/Dockerfile:

FROM node:22-bookworm-slim AS builder

WORKDIR /app

COPY package*.json ./

RUN npm ci

COPY . .

RUN npm run build

FROM nginx:stable-alpine

COPY --from=builder /app/dist /usr/share/nginx/html

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

Create frontend/.dockerignore:

node_modules
dist
.git
.env

Build the image from the parent directory:

docker build -t my-frontend:1.0 ./frontend

Run the container:

docker run -d --name frontend -p 8080:80 my-frontend:1.0

Open http://localhost:8080.

Why use Nginx for frontend applications?

A production React application usually contains static HTML, CSS, and JavaScript files. Nginx can serve these files without requiring a Node.js server at runtime.

Next.js applications that require server-side rendering or dynamic server functionality generally need a Node.js runtime instead.

Containerizing a Backend Application

A backend application needs its runtime, dependencies, and startup command.

A typical backend project contains:

backend/
├── server.js
├── package.json
├── package-lock.json
└── Dockerfile

Create backend/Dockerfile:

FROM node:22-bookworm-slim

WORKDIR /app

COPY package*.json ./

RUN npm ci --omit=dev

COPY . .

ENV NODE_ENV=production
ENV PORT=4000

EXPOSE 4000

CMD ["npm", "start"]

Ensure the package manifest defines a valid start script and the application listens on port 4000 at 0.0.0.0.

Build the image:

docker build -t my-backend:1.0 ./backend

Run it:

docker run -d --name backend -p 4000:4000 my-backend:1.0

The API is accessible at http://localhost:4000.

Passing environment variables

docker run -d \
  --name backend \
  -p 4000:4000 \
  -e NODE_ENV=production \
  -e PORT=4000 \
  my-backend:1.0

Your application can read these values through process.env.

Never bake private API keys or production passwords into a Docker image.

Running a Database Container

Docker provides official images for databases such as PostgreSQL and MySQL.

For PostgreSQL:

docker run -d \
  --name postgres-db \
  -e POSTGRES_USER=appuser \
  -e POSTGRES_PASSWORD=local-dev-password \
  -e POSTGRES_DB=appdb \
  -p 127.0.0.1:5432:5432 \
  postgres:17

This is a local development example. Use secure credentials and suitable secret management in production.

Check the database logs:

docker logs postgres-db

Connect to PostgreSQL:

docker exec -it postgres-db psql -U appuser -d appdb

Persisting database data

Use a named volume to keep database files outside the container's writable layer.

docker volume create postgres-data

Start PostgreSQL with the volume:

docker run -d \
  --name postgres-db \
  -e POSTGRES_USER=appuser \
  -e POSTGRES_PASSWORD=local-dev-password \
  -e POSTGRES_DB=appdb \
  --mount source=postgres-data,target=/var/lib/postgresql/data \
  postgres:17

Named volumes can survive container deletion. However, they are not backups, and deleting the volume itself removes its data.

Docker Networking

Containers need a network to communicate with each other.

Create a user-defined bridge network:

docker network create app-network

Start PostgreSQL on that network:

docker run -d \
  --name postgres-db \
  --network app-network \
  -e POSTGRES_USER=appuser \
  -e POSTGRES_PASSWORD=local-dev-password \
  -e POSTGRES_DB=appdb \
  --mount source=postgres-data,target=/var/lib/postgresql/data \
  postgres:17

Start the backend on the same network:

docker run -d \
  --name backend \
  --network app-network \
  -p 4000:4000 \
  -e DATABASE_URL=postgresql://appuser:local-dev-password@postgres-db:5432/appdb \
  my-backend:1.0

The backend can reach PostgreSQL using postgres-db as its hostname because both containers share the user-defined network.

Your backend must read DATABASE_URL, use a PostgreSQL client library, and handle connection failures appropriately.

Important networking concepts

  • localhost inside a container refers to that container itself.

  • Containers on the same user-defined bridge network can communicate using container names.

  • -p publishes a container port to the host.

  • Containers do not need host-published ports to communicate over their shared internal network.

For browser-based frontend applications, API requests must use a browser-accessible address, such as http://localhost:4000, or an appropriately configured reverse-proxy route. The browser cannot normally resolve Docker-only container names.

Useful commands:

docker network ls
docker network inspect app-network
docker network connect app-network backend
docker network disconnect app-network backend

Docker Volumes and Bind Mounts

Volumes and bind mounts let containers access data outside their writable layers.

Named volumes

docker volume create app-data
docker volume ls
docker volume inspect app-data

Use a named volume when application data must persist beyond the lifecycle of a container.

Bind mounts

A bind mount connects a host directory to a container directory.

docker run -d \
  --name local-nginx \
  --mount type=bind,source="$(pwd)/html",target=/usr/share/nginx/html,readonly \
  -p 8080:80 \
  nginx:stable

The host directory must exist, and the application needs appropriate filesystem permissions.

Interview point: Named volumes are managed by Docker, while bind mounts use an explicit path on the host.

Docker Image Optimization

A production image should contain only what the application needs to run.

Use .dockerignore

node_modules
.git
.env
coverage
dist

Adjust the file to avoid excluding anything required by the build.

Optimize layer caching

Copy dependency manifests before the rest of the application:

COPY package*.json ./
RUN npm ci
COPY . .

This lets Docker reuse the dependency installation layer when only source files change.

Use multi-stage builds

Multi-stage builds separate build dependencies from runtime dependencies.

For example, a React application can be built in a Node.js stage and served from an Nginx stage. The final image does not need the Node.js build tools.

Use suitable base images

Choose supported images that meet your application's compatibility and security requirements.

Run as a non-root user

Where practical, configure the application to run with only the permissions it needs.

Useful commands:

docker images
docker history my-backend:1.0
docker system df

Debugging Docker Containers

Container exits unexpectedly

docker ps -a
docker logs backend

Check the startup command, missing dependencies, environment variables, and application errors.

Port is already in use

docker run -d -p 4001:4000 my-backend:1.0

This maps host port 4001 to container port 4000.

Container cannot connect to another service

docker network inspect app-network
docker logs backend
docker inspect backend

Verify network membership, the destination hostname, the container port, and whether the target application is ready.

Application changes are not appearing

Build a new image:

docker build -t my-backend:1.1 ./backend

Then recreate the container using the new image. Rebuilding an image does not automatically update containers already created from an older image.

Container resource usage

docker stats

This displays live CPU, memory, and network statistics for running containers.

Pushing Images to Docker Hub

To share an image, create a repository on Docker Hub.

Authenticate:

docker login

Tag your image:

docker tag my-backend:1.0 username/my-backend:1.0

Push it:

docker push username/my-backend:1.0

Pull it on another machine:

docker pull username/my-backend:1.0

Run it:

docker run -d -p 4000:4000 username/my-backend:1.0

The target machine must also have the required environment variables, network access, and any external dependencies.

Publishing an image does not automatically deploy the application.

Docker Security Best Practices

  • Never hardcode credentials in a Dockerfile.

  • Keep base images and application dependencies updated.

  • Prefer specific image versions for reproducible builds.

  • Run applications as non-root users when practical.

  • Avoid publishing database ports unnecessarily.

  • Exclude secrets and unrelated files from the build context.

  • Scan images for known vulnerabilities.

  • Treat Docker daemon access as a privileged permission.

Docker Interview Questions

What is Docker?

Docker is a platform for building, distributing, and running applications in containers.

What is the difference between an image and a container?

An image is a packaged template. A container is an instance created from that image.

What is the difference between docker run and docker start?

docker run creates and starts a new container. docker start starts an existing stopped container.

What is the difference between RUN and CMD?

RUN executes during image building, while CMD defines the default command executed when the container starts.

What is the difference between CMD and ENTRYPOINT?

CMD supplies a default command or arguments. ENTRYPOINT defines the primary executable.

What is the purpose of .dockerignore?

It excludes unnecessary files from the build context, reducing build overhead and helping prevent accidental inclusion of local files or secrets.

Why does a backend container use a database container's name instead of localhost?

On a shared user-defined network, Docker resolves container names to the corresponding network addresses. localhost inside the backend container refers to the backend container itself.

What is the difference between a volume and a bind mount?

A named volume is managed by Docker. A bind mount connects a specified host path to a container path.

Why use multi-stage builds?

They separate the build environment from the runtime environment, allowing the final image to omit unnecessary build tools and dependencies.

What happens when you delete a container?

The container and its writable layer are removed, but the image and independently stored named-volume data can remain.

Does EXPOSE publish a port?

No. EXPOSE documents the intended container port. Use -p when you need to publish that port to the host.

How do you debug a container?

Use docker ps -a, docker logs, docker inspect, and docker exec to investigate its state, logs, configuration, and running environment.

Does Docker replace virtual machines?

Not completely. Containers generally share the host kernel, while virtual machines run separate guest operating systems. Both technologies have useful roles.

Do you need Kubernetes to use Docker?

No. Docker containers can run on a single machine without Kubernetes. Kubernetes is a separate container orchestration platform.

Conclusion

To become confident with Docker, focus on the concepts that directly affect building, running, debugging, and deploying applications:

  • Dockerfiles, images, and containers.

  • Image building and container lifecycle commands.

  • Port mapping and networking.

  • Environment variables and persistent storage.

  • Frontend and backend containerization.

  • Multi-stage builds and image optimization.

  • Logging, troubleshooting, and image publishing.

Practice by containerizing a real full-stack application, connecting its backend to PostgreSQL, and publishing the resulting images to Docker Hub.

For multi-container application configuration and orchestration, continue with the separate Docker Compose Complete Guide: From Beginner to Interview Ready.

Official references: Docker Documentation, Dockerfile Reference, Build Best Practices, and Docker Networking.