Docker - 1
The problem was, to share the software or code you need to setup and install dependencies on other machines as well. This give rise to a lot issues and conflicts related to dependencies and especially -> “It works on my machine!”.
Docker solves this by creating a wrapper over your code and call it as a container. Now you share this container in the form of a file or configuration known as image.
Other users run this image and get the container running isolated from their machine with the exact same code and dependencies and just dump the container and image whenever they want.
Image vs Container
- Image is like a blueprint or a configuration file for the machinery (container).
- Container is the machine that is running on the machine based on the above image.
Commands
To run any container we need to run the following command
docker run <image_name>
The container will run as long as there is at least one main process running inside it. If you try to run an ubuntu container using the above command it will start and destroy the container the very next second.
Therefore we use flags such as
docker run -it ubuntu:latest
-
-it: is a flag and combination of 2 things,-i(interactive) and-t(TTY terminal). This gives you control of interactive terminal for the running container. -
latest: this is the version, so we get the latest version of ubuntu.
So basically the process is
docker run <image_name>
│
▼
Is the image available locally?
│
┌────┴────┐
YES NO
│ │
▼ ▼
Use local Pull image
image from registry
│ │
└────┬─────┘
▼
Create a new container
│
▼
Run the container
Now to execute the commands inside the running container, we make use of exec command.
docker exec <container_name_or_id> <command>
The next question is how to expose and access the running applications inside the container. We make use of port mapping using -p flag.
docker run -it -p <machine_port><container_port> ubuntu:latest
You can expose any number of ports you want.
Sometimes we need to pass environment variables to the containers. For that we make use of -e flag.
docker run -it -p <machine_port><container_port> -e <key><value> ubuntu:latest
How to dockerise the applications?
We create Dockerfile and write the configuration to run the container.
Steps
- We bring the base image (it maybe a bare metal ubuntu image or a node image depends on the usecase) using
FROM. - We then copy our code inside the container using
COPY. Install the required deps usingRUN. - At last we set the
ENTRYPOINT, which will always run when someone starts the container.
Example
FROM ubuntu
# 1. Install curl, git, and the required compilation tools (build-essential)
RUN apt update && apt install -y curl git build-essential
# 2. Set the environment variables for n-install before running the script
ENV N_PREFIX="/root/n"
ENV PATH="$N_PREFIX/bin:$PATH"
# 3. Install Node.js version 24 using n-install
RUN curl -fsSL https://raw.githubusercontent.com/mklement0/n-install/stable/bin/n-install | bash -s -- -y 24
COPY package.json package.json
COPY package-lock.json package-lock.json
COPY index.js index.js
# 4. Now npm will be found natively
RUN npm install
COPY index.js index.js
# Fixed the syntax for ENTRYPOINT (added comma separating strings)
ENTRYPOINT ["node", "index.js"]
After creating this file, we can create an image from it and publish to dockerhub if we want.
To create an image from Dockerfile
docker build -t <image_name> <image_path>
Now you can simply run this image and pass the ports and env as usual.
Caching
Docker follows layer caching. This means if you change a particular step all the steps or layer under it will be rebuild again. Therefore we need to be careful when writing dockerfile.
Dockerfile
│
▼
┌──────────────┐
│ FROM node │ ← Layer 1
└──────────────┘
│
▼
┌──────────────┐
│ COPY package │ ← Layer 2
│ .json │
└──────────────┘
│
▼
┌──────────────┐
│ RUN npm │ ← Layer 3
│ install │
└──────────────┘
│
▼
┌──────────────┐
│ COPY . . │ ← Layer 4
└──────────────┘
If nothing changes
Layer 1 ✓ cached
↓
Layer 2 ✓ cached
↓
Layer 3 ✓ cached
↓
Layer 4 ✓ cached
If package.json changes
Layer 1 ✓ cached
↓
Layer 2 ✗ changed
↓
Layer 3 ✗ rebuilt
↓
Layer 4 ✗ rebuilt
Docker compose
In production or big applications we might need to run multiple images at once to run an app. In such cases we make use of docker-compose.yml file. You can think of it as an orchestrator of containers.
Example
version: '3.8'
services:
postgres:
image: 'postgres'
ports:
- 5432:5432
environment:
<key>:<value>
redis:
image: 'redis'
ports:
- 6379:6379
Now we can run the following command to run all these containers at once
docker compose up
To bring all of them down
docker compose down