Docker Basics and Best Practices Guide
Docker Basics and Best Practices Guide
00:00 Intro
This guide covers Docker basics and advanced topics in a casual, easy-to-understand way. We’ll explain key
concepts like containers, images, and Dockerfiles, and walk through building a sample [Link] + TypeScript
application in Docker. Examples will show Docker commands ( docker build , docker run , etc.) and
relevant configuration (Dockerfiles and [Link] ). Each section ends with best practices and
interview-style questions to test your understanding.
Interview Questions:
- What is Docker used for?
- How does Docker improve development and deployment?
Examples:
- Building: docker build -t myapp:1.0 . builds an image from the Dockerfile.
- Running: docker run -d -p 3000:3000 myapp:1.0 runs a container mapping port 3000.
- Sharing: After building, use docker push to upload the image to a registry, so others can
docker pull it.
Best Practices:
- Use official base images (e.g. node:18 ) when possible.
- Keep your Dockerfiles simple and version-controlled.
- Rebuild images frequently to update dependencies and get security fixes.
Interview Questions:
- What is Docker and what problems does it solve?
- Why package an application in a Docker container?
- How does Docker separate applications from infrastructure?
1
02:32 Why We Need Docker
We need Docker because it standardizes environments and simplifies deployment. Traditionally,
developers said “it works on my machine” when code ran only on their setup. Docker solves this: containers
include all required libraries and settings, so an app runs the same everywhere 2 1 . This consistency
speeds up development and testing and fits well with CI/CD pipelines 1 .
Docker also improves resource efficiency. Containers are lightweight – they share the host OS kernel and
include only needed components 3 . That means multiple containers can run on one server where only a
few full VMs would fit. Many big companies use Docker to reduce costs and run more workloads on the
same hardware 4 5 .
Examples:
- Portability: A containerized app can run on your laptop, on a company server, or on cloud services without
changes 1 6 .
- Speed: Using Docker, builds and tests happen in isolated containers, so you can push fixes and redeploy
very quickly 7 .
Best Practices:
- Always test images after building (use docker run --rm for quick tests).
- Tag images with meaningful versions, not just latest .
- Use Docker in CI pipelines to ensure consistent builds.
Interview Questions:
- Why is Docker beneficial for development and deployment?
- What issues does containerization solve compared to traditional deployments?
- How does Docker improve resource usage compared to virtual machines?
Examples:
- You might run a [Link] app and an Nginx server in separate containers on the same host, and each will
have its own filesystem and libraries.
- If you copy a container to a new machine, it runs identically because it carries its environment with it.
Best Practices:
- Keep containers small by starting from minimal base images.
- Only include necessary dependencies – extra software slows down startups and increases image size.
- Use multi-stage builds to strip out build tools (see Dockerfile section).
2
Interview Questions:
- What is a container in Docker?
- How is a container different from a virtual machine?
- What kinds of things go inside a container image?
Docker Desktop (on Mac/Windows) bundles everything: the daemon, CLI, Docker Compose, Kubernetes,
and credential helpers 10 . This makes it easy to install on a local machine.
Docker also uses registries and images: A Docker registry (like Docker Hub) stores images 11 . When you
docker pull or docker run , Docker fetches the required image from the registry 11 . Once an image
is on the host, you start it by running a container from that image.
Example Tools:
- Docker daemon ( dockerd ): manages images, containers, networks.
- Docker CLI ( docker ): user interface for Docker commands.
- Docker Compose: another CLI for orchestrating multi-container apps.
- Docker Hub: default public registry (you can push/pull images).
Best Practices:
- Keep Docker Engine up to date for security patches.
- Use Docker Desktop for Mac/Windows; on Linux install Docker Engine via package manager.
- Minimize privilege (don’t run daemons as root if possible).
Interview Questions:
- What are the main components of Docker’s architecture?
- What is Docker Desktop and what does it include?
- How does docker pull / push relate to registries and images?
11:31 Docker vs VM
Containers and virtual machines (VMs) both provide isolation, but at different layers. A VM virtualizes the
hardware, running a full guest OS on top of a hypervisor. In contrast, a Docker container virtualizes at the
OS level: it shares the host’s kernel and isolates processes using OS features (namespaces, cgroups) 3 .
In simple terms, a VM is a whole computer in software, whereas a container is just an isolated user-space.
This makes containers much lighter. You can spin up dozens of containers on a host because they don’t
need their own OS instances. Docker itself says containers are “lightweight and fast” and use more of the
server’s resources than hypervisor-based VMs 5 .
3
Key Differences:
- Startup time: Containers start almost instantly; VMs take longer to boot a full OS.
- Resource use: Containers share libraries and the kernel, so they have less overhead. VMs duplicate OS,
using more RAM and disk.
- Portability: Containers carry only the app and its dependencies, making them highly portable across
environments.
Best Practices:
- Use containers for microservices and scalable applications.
- Use VMs if you need full OS isolation or must run a different OS.
- Don’t mix heavy GUI apps in containers (they’re best for headless services).
Interview Questions:
- How is a Docker container different from a VM?
- Why are containers generally faster to start than VMs?
- When might you still choose a VM over Docker?
This flow ensures that from development to production, you’re using the same container image. Many
teams also automate this in CI/CD pipelines so that every code change triggers a rebuild and redeploy of
the container.
Best Practices:
- Tag images with semantic versions, not just latest .
- Automate builds using docker-compose or build scripts.
- Scan images for vulnerabilities regularly.
Interview Questions:
- What are the steps to go from source code to running container in production?
- What does docker build -t name:tag . do?
- How do you share a built image with others?
4
14:17 What is Dockerfile
A Dockerfile is a plain text file containing instructions to build a Docker image 15 . Each line in a Dockerfile
is a command (like FROM , COPY , RUN ) that builds up the image layer by layer. The first instruction is
usually FROM , which specifies a base image (e.g. node:18 or ubuntu:24.04 ). You then add your code
and dependencies. For instance, a [Link] Dockerfile might look like:
When you run docker build , Docker reads this Dockerfile and executes each instruction in order. The
result is a new image you can run.
Best Practices:
- Multi-stage builds: Use multiple FROM lines to build an app in one stage and create a slimmer final
image. This keeps the final image small 16 .
- Minimize layers: Combine commands (e.g. use && ) where it makes sense, and order operations to take
advantage of build cache.
- Use .dockerignore: Exclude files/folders that aren’t needed in the image (like node_modules in the
context if you install them in Docker).
Interview Questions:
- What is a Dockerfile and what is it used for?
- Name some common Dockerfile instructions (e.g. FROM , RUN , CMD ).
- How does a multi-stage build work and why use it?
5
14:57 What is Docker Registry
A Docker registry is a server-side application for storing and distributing Docker images. The default public
registry is Docker Hub, which anyone can use for free to pull or push images. When you run
docker pull imageName or docker run imageName , Docker fetches the image from a registry (Hub
by default) 11 14 . If you run docker push myuser/myapp:tag , Docker uploads that image to the
registry under your account 11 13 .
You can also run private registries (e.g. GitHub Container Registry, AWS ECR, or a self-hosted Docker
Registry) for proprietary images. Before pushing to any registry, authenticate with docker login .
Examples:
- docker push myuser/myapp:1.0 uploads the tagged image to Docker Hub 13 .
- docker pull postgres:14 downloads the official PostgreSQL 14 image from Docker Hub.
Best Practices:
- Use meaningful image names (often includes your Docker Hub username or organization).
- Keep access to private registries secure; use proper credentials.
- Clean up old images in the registry to save space.
Interview Questions:
- What is Docker Hub? How do you publish an image to it?
- Why might you use a private registry?
- How do docker push and docker pull work?
Linux: You typically install the Docker Engine via the package manager. For example, on Ubuntu:
This sets up the Docker daemon and CLI on your system 17 . After installation, add your user to the
docker group (or use sudo ) to run Docker commands. On some distros you might use yum , dnf , or
pacman instead.
Examples:
- Install on Ubuntu (from Docker repo): sudo apt-get install docker-ce docker-ce-cli
[Link] 17 .
6
- Start Docker on boot: sudo systemctl enable docker && sudo systemctl start docker .
- Verify: docker version should show client and server versions.
Best Practices:
- Always install Docker from official repositories or Docker’s own repo, not from arbitrary sources.
- Keep Docker up to date with the latest stable release.
- After installing Docker Desktop on Mac/Win, configure WSL2 (Windows) or virtualization (Mac) as
instructed.
Interview Questions:
- How do you install Docker on Windows or Mac?
- What command installs Docker on Ubuntu?
- What is Docker Desktop and why might you use it on Windows?
This command bootstraps a new React project named my-web-app with TypeScript support 18 . It creates
the basic files ( [Link] , src/[Link] , etc.) and installs dependencies. You can run the app with
npm start locally to verify it works (it typically opens a browser on [Link] ).
(Alternatively, for a [Link] API, you might run npm init -y and install Express, but here we stick with the
React example.)
Next Steps: We will write a Dockerfile for this project, build an image, and run it in a container.
Interview Questions:
- How do you create a new React project with TypeScript?
- What files/directories does Create React App generate?
- How would you start the dev server for this React app?
7
RUN npm install
COPY . .
RUN npm run build
1. Builder stage: starts from the official Node image. It installs dependencies and runs npm run
build to produce static files.
2. Final stage: starts from Nginx (smaller), and copies the build output from the first stage into Nginx’s
html folder. It exposes port 80 and runs Nginx.
This is an example of a multi-stage build, which reduces the final image size by discarding build-time tools
16 .
Best Practices:
- Use multi-stage builds to keep images lean 16 .
- Choose small base images (like nginx:alpine or node:18-alpine ) when possible.
- Only copy the files you need (use .dockerignore to skip dev files).
Interview Questions:
- What does the FROM instruction do in a Dockerfile?
- Explain the multi-stage build above.
- Why might you use Nginx in the final stage for a React app?
This command reads the Dockerfile, executes each step, and produces a new image. The output shows each
build step and its result.
Examples:
- Tagging: docker build -t username/my-web-app:latest . to prepare for pushing.
- No cache: add --no-cache if you want to force rebuilding from scratch.
- Verbose: use docker build --progress=plain . for detailed logs.
8
Best Practices:
- Always tag your images (avoid ambiguous latest ).
- Rebuild often to incorporate security patches.
- Use .dockerignore to speed up builds by excluding unneeded files.
Interview Questions:
- What does docker build -t name:tag . do?
- How can you speed up rebuilds when working on a Dockerfile?
- Why are image tags important?
# Run in detached mode, map port 3000 on host to port 3000 in container
docker run -d --name my-app -p 3000:3000 my-web-app:1.0
# Stop a container
docker stop my-app
# Remove a container
docker rm my-app
Best Practices:
- Use --rm if you want the container removed automatically after exit (good for one-off runs).
- Name containers for easier management.
- Don’t forget to stop and remove old containers to free resources.
Interview Questions:
- How do you start a container and map its port to the host?
9
- What command lists active Docker containers?
- How do you get a shell inside a running container?
• Inspect: docker inspect <container> displays detailed JSON about its configuration (network
settings, mounts, environment, etc.).
• Stats: docker stats shows live resource usage (CPU, memory) of running containers.
• Logs: docker logs -f <container> follows the container’s output in real time.
• Attach: docker attach <container> attaches your terminal to a container’s STDIN/STDOUT
(for attached processes).
• Copy files: docker cp hostfile container:/path (or vice versa) copies files between host
and container.
• Top: docker top <container> shows processes running in the container.
These commands help you debug or gather info. For example, if a container isn’t working, docker
inspect can show if it’s missing an environment variable or if ports are bound correctly.
Best Practices:
- Label your containers (using --label ) to make them easier to find.
- Use docker logs to investigate crashes or errors.
- Use docker commit sparingly; it’s better to rebuild from a Dockerfile than to snapshot a running
container.
Interview Questions:
- How can you view the configuration of a container (e.g. its port mapping)?
- What command shows real-time stats (CPU/memory) for containers?
- How do you transfer files between the host and a container?
10
docker image prune # removes dangling images
docker system prune -a # removes all unused images, stopped containers, etc.
Best Practices:
- Regularly prune unused images ( docker image prune -a ) to save disk space.
- Keep images small (smaller images mean less space and faster downloads).
- Avoid leaving dangling or redundant images after rebuilds.
Interview Questions:
- How do you list all Docker images on your machine?
- What is a dangling image and how do you remove it?
- How does docker system prune help with cleaning up Docker resources?
Alternatively, during development you might mount your code directory into the container (bind mount) so
you don’t need to rebuild each time (see the Mount Binds section below).
Best Practices:
- Don’t use latest for production updates; use version tags so you can roll back if needed.
- Test each new image version before pushing live.
- Automate rebuilds with CI/CD on code changes.
Interview Questions:
- If you change the application code, what Docker commands do you run to update the container?
- How can docker-compose help with redeploying updates?
- Why might you tag images with version numbers instead of latest ?
11
01:14:58 Pre-defined Images
Docker Hub hosts thousands of pre-defined images, including official and verified images for common
software (Node, Python, databases, etc.). These are curated and maintained by Docker or trusted
publishers. For example, the Node official image ( node:18-alpine ) already has [Link] installed. Using
official images is a best practice because they are regularly updated and documented 20 .
To use one, simply reference it in your Dockerfile or pull it. For instance, FROM node:18 in your Dockerfile
starts from the official Node 18 image.
Best Practices:
- Prefer official or verified images on Docker Hub 20 .
- Always specify a tag (like node:18-alpine ) rather than using node:latest .
- Keep base images updated by rebuilding when a new base tag is released.
Interview Questions:
- What are Docker Official Images and why use them?
- How would you find an image for a database (e.g. Postgres) on Docker Hub?
- Why avoid latest tag in production?
This command:
- Pulls the ubuntu:latest image (if not local).
- Runs a new container and starts a Bash shell.
- -it keeps STDIN open and allocates a TTY so you get a shell prompt.
- --rm means the container will be removed when you exit.
Inside that shell, you can run commands as if on a mini-machine. To exit, type exit or press Ctrl+D.
Alternatively, you can start a container normally and then use docker exec -it my-container bash
to open a shell in a running container.
12
Best Practices:
- Use interactive mode for debugging or one-off tasks.
- Avoid using it for normal daemon processes (use -d for those).
- Remember to remove containers ( --rm ) to avoid clutter.
Interview Questions:
- What do the -i and -t options do when running a container?
- How can you start a container and then get an interactive shell in it?
- What’s the difference between docker attach and docker exec -it ?
This names the image under your Docker Hub username (or organization).
docker login
Docker uploads all layers to the registry. Once done, the image is available at [Link]/
myusername/my-web-app:1.0 13 .
After pushing, anyone (with access) can do docker pull myusername/my-web-app:1.0 to get it.
Best Practices:
- Use docker login with a machine user or service account for automation.
- Don’t put secrets (API keys, passwords) in images – they’ll be visible to anyone who pulls it.
- Regularly clean up old tags in your repositories.
Interview Questions:
- How do you publish a Docker image to Docker Hub?
13
- What is the command to upload an image once it’s tagged?
- Why should you tag your image with a username/repo?
This command downloads the node:18-alpine image from Docker Hub to your machine. If you omit the
tag (e.g. docker pull debian ), Docker uses the :latest tag by default 14 . The output will show
each layer being downloaded. Once pulled, you can run the image without downloading again (e.g.
docker run node:18-alpine node -v ).
Note: If you run docker run someImage and the image isn’t found locally, Docker will automatically try
to pull it from the registry.
Best Practices:
- Specify version tags to ensure you get the correct image.
- docker pull -a imagename fetches all tags of a repository (optional).
- Regularly docker pull base images to get security updates for your builds.
Interview Questions:
- How do you retrieve a Docker image from Docker Hub?
- What happens if you docker run an image that isn’t present locally?
- What default tag does Docker use when none is specified?
# Run a container that uses the volume (e.g. a Node app writing to /data)
docker run -d --name my-app -v my-vol:/data my-web-app:1.0
This mounts the named volume my-vol at /data inside the container. Data written there remains on
the host even if the container is removed. You can list volumes with docker volume ls and inspect with
docker volume inspect my-vol .
14
Why volumes? They are isolated from the container’s lifecycle and don’t increase the image size 22 . They
also work across platforms and can be more safely shared. Docker keeps volumes unless you explicitly
remove them, so your data persists.
Best Practices:
- Use named volumes for databases or any data you want to keep (e.g. -v dbdata:/var/lib/mysql ).
- Don’t store persistent data in a container’s writable layer (it vanishes when the container is removed).
- Remove unused volumes with docker volume prune to free space (dangling volumes are not auto-
deleted) 19 .
Interview Questions:
- What is a Docker volume and why use it?
- How do you mount a volume into a container?
- What’s the difference between a volume and storing data in the container’s filesystem?
This mounts the current host directory ./app into /app inside the container. Changes you make on the
host (in ./app ) are immediately visible inside the container. This is great for development: you can edit
code on your machine and have the container use the new code without rebuilding the image.
However, bind mounts depend on the host’s directory structure and permissions, unlike Docker-managed
volumes 23 . Be careful with absolute paths.
Best Practices:
- Use bind mounts ( -v /host/path:/container/path ) for code development or when the container
needs to read host files.
- For production data, prefer named volumes unless you explicitly need host access.
- Always use full paths or $(pwd) to avoid ambiguity.
Interview Questions:
- How do you share a directory from the host with a container?
- When would you use a bind mount instead of a volume?
- What does -v $(pwd):/app do?
15
// src/[Link]
import express from 'express';
const app = express();
[Link]('/api/hello', (req, res) => {
[Link]({ message: 'Hello from the API container!' });
});
[Link](3000, () => [Link]('API listening on port 3000'));
You would write a Dockerfile similar to before, exposing port 3000 and running the built JS output. For
example:
FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build # compile TypeScript
EXPOSE 3000
CMD ["node", "dist/[Link]"]
Clients (browsers or other services) can access the API at [Link] if you
mapped port 3000.
Best Practices:
- Expose only the API port that needs to be public.
- For production, set NODE_ENV=production and compile TS in the build step.
- Use small, official base images (e.g. node:18-alpine ) for your API image.
Interview Questions:
- How do you expose a REST API running in a Docker container to the host?
- How would you include environment variables (e.g. PORT) in a Docker container?
- What is CORS and do you need to consider it in containers (answer: CORS is unrelated to containers, it’s an
app concern)?
16
example, if you have a Postgres or MongoDB running on your laptop, you can set the API’s DB host to
[Link] . This DNS points to the host’s IP from within the container 24 .
Inside the container, an environment variable DB_HOST=[Link] tells the app to connect
back to the host database. (On Linux, you might use --network host or the host’s gateway IP instead.)
If you prefer, you can run the database in a container too (see the next section). The key idea is: containers
can reach host services via [Link] (Mac/Win) or configured networking.
Best Practices:
- For development, [Link] is convenient. In production, use containerized DB or
managed DB service.
- Do not store production DB data inside a container’s filesystem; always use volumes or managed
databases.
- Keep credentials (passwords) out of Dockerfiles – pass them via environment variables or secret stores.
Interview Questions:
- How can a Docker container connect to a database running on the host machine?
- What is [Link] used for?
- How would you handle database credentials securely in Docker?
# Create a network
docker network create app-net
Here, the Node app uses mongodb as the host name for the database (because containers on the same
user-defined network can reach each other by name 25 26 ). This setup is effectively a multi-container
17
project. The containers can be stopped and removed independently, and you can restart only the parts that
changed.
Best Practices:
- Use a user-defined bridge network (as above) instead of the default bridge so you get automatic DNS by
container name.
- Always specify network alias or container names when referencing services.
- Clean up unused containers and networks ( docker network prune ) when done.
Interview Questions:
- How do you run two containers so that one can talk to the other?
- What does --network do in docker run ?
- What is the default network for Docker containers if you don’t specify one?
When containers share a network, they can see each other’s exposed ports by their names. For example, a
container pinging the name db (as above) connects to the DB container’s IP 25 .
Best Practices:
- Use named networks to group related containers.
- Avoid the default bridge if you need cross-container DNS; create your own.
- Only expose ports ( -p ) for services that need outside access; internal communication is on the network.
Interview Questions:
- What network drivers does Docker provide by default?
- How do containers communicate on the same user-defined network?
- What does --network host do when running a container?
18
02:30:04 What is Docker Compose
Docker Compose is a tool for defining and running multi-container applications with a single YAML file
28 . You write a [Link] that lists your services (containers), their images or build
instructions, ports, volumes, and networks. Then docker-compose up -d creates and starts all the
services for you.
Compose makes it easy to manage the entire stack of your application in development and testing. You can
control it with commands like: - docker-compose up (start all services)
- docker-compose down (stop and remove containers, networks, volumes)
- docker-compose ps (list running services)
- docker-compose logs (view aggregated logs)
Best Practices:
- Version control your [Link] alongside your code.
- Use the latest Compose file format ( version: "3" or higher).
- Define only what’s needed (services, networks, volumes).
Interview Questions:
- What is Docker Compose used for?
- How do you start an app’s containers using Compose?
- Where do you define services, networks, and volumes in Compose?
version: '3.9'
services:
web:
build: ./my-web-app
ports:
- "3000:80"
environment:
- NODE_ENV=production
db:
image: mongo:6
volumes:
- dbdata:/data/db
19
volumes:
dbdata:
This defines two services: web (built from ./my-web-app with port mapping) and db (using the official
Mongo image, with a named volume). By running docker compose up -d , Compose builds the web
image, starts both containers on a default network, and sets up the volume.
Best Practices:
- Use build: if you want Compose to build an image from a Dockerfile.
- Declare volumes: under each service and at top-level for named volumes.
- Use depends_on: if one service must start before another (e.g. db ).
Interview Questions:
- How do you configure multiple services in [Link] ?
- What is the purpose of the build vs image keys in Compose?
- How do you define environment variables or volumes in Compose?
services:
api:
image: my-api:1.0
networks:
- app-net
client:
image: my-client:1.0
networks:
- app-net
networks:
app-net:
driver: bridge
Here we create a network app-net (using the bridge driver) and attach both api and client to it.
Now api can reach client (and vice versa) by service name on app-net .
If you omit the networks: key under services, Compose puts all services on a default network. Using
named networks can help isolate parts of your application or connect to existing Docker networks.
Best Practices:
- For simple apps, the default network is fine. Use custom networks if you have complex needs.
20
- You can set driver: host for special cases (mostly on Linux).
- Use external: true if you want to use an existing Docker network created outside Compose.
Interview Questions:
- How do you define and use a custom network in Compose?
- What is the default network created by Docker Compose?
- How can you connect a Compose service to an external (pre-existing) network?
version: '3'
services:
db:
image: postgres:15
environment:
- POSTGRES_PASSWORD=secret
volumes:
- db-data:/var/lib/postgresql/data
app:
build: ./my-app
ports:
- "4000:4000"
volumes:
db-data:
This configuration creates a named volume db-data and mounts it into the db service. The app
service has no volumes here. When you run docker compose up , Compose will create the db-data
volume automatically (if not existing) and use it for the db container. Data in db-data persists across
container restarts or recreations.
You can also bind-mount host directories in Compose by giving a host path instead of a volume name (e.g.
- ./config:/app/config ).
Best Practices:
- Declare named volumes for any stateful service (databases, etc.).
- Use relative host paths for code mounts only in development, not in production Compose files.
- Clean up volumes with docker compose down -v if you want to remove them when tearing down.
Interview Questions:
- How do you define a volume in [Link] ?
21
- What’s the difference between the volumes: section under a service vs at the bottom of the file?
- How do you share a host directory with a container in Compose?
services:
web:
image: my-nginx
ports:
- "8080:80"
api:
image: my-api
ports:
- "4000:4000"
This maps port 8080 on the host to port 80 in the web container, and maps port 4000 to 4000 for api .
Now you can access the services via [Link] and [Link] .
The expose: key (without a host port) can be used to tell Docker that a container listens on a port, but
doesn’t publish it – it’s useful for inter-service communication only. Most of the time you’ll use ports as
above to allow outside traffic.
Best Practices:
- Only publish the ports you need. In production, consider using a reverse proxy instead of exposing many
ports.
- Be careful with host port collisions (only one container can bind to the same host port).
- If using Docker Compose for development, avoid using privileged ports (<1024) without need.
Interview Questions:
- How do you publish container ports to the host using Docker Compose?
- What is the difference between ports: and expose: in Compose?
- Why might you choose to bind a different host port than the container port?
22
1 5 7 9 10 11 What is Docker? | Docker Docs
[Link]
23