Docker
Docker images
Docker images are the blueprints or templates used to create Docker containers.
An image contains everything required to run an application, such as:
- Application code
- Dependencies
- Libraries
- Runtime
- Configuration
An image is read-only and is used to create one or more containers.
Docker container
Docker container is a running instance of a Docker image.
If Docker image is the blueprint, Docker container is the actual running application created using that blueprint.
For example:
Docker Image → Docker Container →Running Application
We can create multiple containers from the same image.
Docker network
Docker network is a communication channel that allows containers to communicate with other containers.
For example, if we have:
React Container → Backend Container → Database Container
The containers need a way to communicate with each other. Docker networks provide this communication.
Containers connected to the same Docker network can communicate with each other using their container/service names.
Docker volumes
Docker volumes are used to store data outside the container’s writable layer.
This is important because containers are temporary. If a container is deleted, the data stored inside it can also be deleted.
Volumes are commonly used for databases.
Example:
PostgreSQL Container → PostgreSQL Volume → Database Data
Even if we remove and recreate the PostgreSQL container, the data can remain in the volume.
Docker workflow
Docker client
Docker client is the UI/interface that we use to interact with Docker.
We can interact with Docker using:
- Command line
- GUI applications
- Docker Desktop
For example:
docker run ubuntu
Here, we are using the Docker CLI to communicate with Docker. The Docker client sends our command to the Docker daemon.
Docker host / Docker daemon
Docker daemon is the background process that manages Docker.
It is responsible for things such as:
- Creating containers
- Starting containers
- Stopping containers
- Creating networks
- Creating volumes
- Building images
The Docker client communicates with the Docker daemon.
Docker Client → Docker Daemon → Containers / Images / Networks / Volumes
Docker registry / Docker Hub
Docker registry is a centralized repository used to store and distribute Docker images.
Docker Hub is the most commonly used for public Docker registry. This works like a library of Docker images.
For example, we can find images for:
Ubuntu
Node.js
Python
PostgreSQL
Redis
Nginx
MongoDB
We can pull an image from Docker Hub:
docker pull ubuntu
We can also publish our own image to Docker Hub using the our own credentials of the Docker hub
How to create a Docker image?
We normally create a Docker image using a Dockerfile. A Dockerfile contains instructions that tell Docker how to build the image.
Most commonly used Dockerfile keywords
FROM
Specifies the base image.
Example:
FROM node:20-alpine
This means we are using Node.js 20 Alpine as the base image.
WORKDIR
Specifies the working directory inside the container.
WORKDIR /app
After this, commands will normally execute from:
/app
COPY
Copies files from our local machine into the Docker image.
COPY . .
This means copy the current directory into the current working directory of the image.
RUN
Executes a command while building the Docker image.
For example:
RUN npm install
This installs the dependencies while the image is being built.
EXPOSE
Specifies the port that the application is expected to listen on.
EXPOSE 3000
Important: EXPOSE does not actually publish the port to the host.
For publishing a port, we use:
docker run -p 3000:3000 image-name
ENV
Defines environment variables inside the image/container.
ENV NODE_ENV=production
ARG
Defines a build-time variable.
ARG VERSION=1.0
ARG is mainly available while building the image.
VOLUME
Specifies a location intended for persistent data.
VOLUME /data
In real projects, volumes are often configured through Docker commands or Docker Compose.
CMD
Specifies the default command that runs when a container starts.
CMD [“node”, “hello.js”]
CMD can be overridden when running the container.
ENTRYPOINT
Defines the main executable of the container.
ENTRYPOINT [“node”]
It is normally used when we want the container to behave like a specific executable.
The main differences between CMD and ENTRYPOINT are:
CMD
· Default command/arguments
ENTRYPOINT
· Main executable
· They can also be used together.
For example:
ENTRYPOINT [“node”]
CMD [“hello.js”]
This effectively runs:
node hello.js
How to pull an image and run it?
1. Create a separate directory
Create a separate directory and open it in your default IDE.
2. Pull the image
Run this command in the terminal or command prompt:
docker pull ubuntu
This pulls the latest available Ubuntu image from the configured registry.
Note: We are pulling an Ubuntu Docker image, not the Ubuntu kernel.
3. Create and run a container
docker run -it ubuntu
Here:
docker run (Create and start a container)
-it (Interactive terminal)
ubuntu (Image used to create the container)
You will now get an Ubuntu terminal inside the container.
For example:
root@container-id:/#
We are now interacting with the Ubuntu environment inside the Docker container.
To leave the container:
exit
How to create our own Docker image?
Let’s create a simple Node.js application.
1. Create the files
Create a separate directory.
Get Sandil Helitha Perera’s stories in your inbox
Join Medium for free to get updates from this writer.
Inside the directory create:
docker-example/
├── Dockerfile
└── hello.js
Inside hello.js:
console.log(“Hello from Docker!”);
2. Create the Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY . .
CMD [“node”, “hello.js”]
Let’s understand what happens:
FROM (Get Node.js environment)
WORKDIR (Create/use /app)
COPY (Copy our application files)
CMD (Run hello.js)
3. Build the image
Run:
docker build -t hello-docker .
Here:
docker build (Build an image)
-t hello-docker (Give the image a name)
. (Use the current directory as the build context)
We now have our own Docker image:
hello-docker
4. Run the image
docker run hello-docker
Output:
Hello from Docker!
How to Dockerize a React application?
Let’s assume we have a React application created using Vite.
1. Create a simple React project
For example:
npm create vite@latest react-docker
Then install dependencies:
npm install
2. Create a Dockerfile
Inside the project:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 5173
CMD ["npm", "run", "dev", " -- ", " -- host", "0.0.0.0"]The important part here is:
— host 0.0.0.0
Vite normally needs to listen on an accessible network interface so that the application can be accessed through the Docker port mapping.
3. Create a .dockerignore file
Create:
.dockerignore
Add:
node_modules/
dist/
.git/We don’t want to copy unnecessary files into the Docker build context.
4. Build the image
docker build -t react-docker .
5. Run the container with port mapping
docker run -p 5173:5173 react-docker
The format is:
-p HOST_PORT:CONTAINER_PORT
Therefore:
5173:5173
means:
Host machine port 5173 → Container port 5173
Now we can access the React application through:
Problem with Docker during development
There is a problem when developing applications using Docker.
Suppose we change App.jsx on our local machine.
The running Docker container may still contain the old version of the code. Therefore, we need to rebuild the image every time we change the code. Hence this is not convenient during development.
So how can we solve this?
The solution is bind mounts / volumes.
Using volumes during React development
First stop the currently running container.
Then run:
docker run -p 5173:5173 -v “$(pwd):/app” -v /app/node_modules react-docker
On Windows, the exact $(pwd) syntax can differ depending on whether you are using PowerShell, CMD, Git Bash, or WSL.
How to publish a Docker image to Docker Hub?
First, we need a Docker Hub account.
1. Login
type: docker login
2. Tag the image
Suppose our local image is:
react-docker
We need to tag it using our Docker Hub username:
docker tag react-docker <username>/react-docker
For example:
myusername/react-docker
The general format is:
<username>/<image-name>
3. Push the image
docker push <username>/react-docker
Now the image is uploaded to Docker Hub.
Other people can pull it using:
docker pull <username>/react-docker
How to use Docker Compose?
When our application becomes more complicated, we may have multiple containers.
For example:
React → Backend → PostgreSQL
Managing every container separately can become difficult. This is where Docker Compose comes in.
Docker Compose allows us to define and manage multiple Docker services using one configuration file.
Creating a Docker Compose project
1. Create a React project
2. Run Docker Init
Inside the project directory:
docker init
Docker will help create files such as:
Dockerfile
.dockerignore
compose.yaml
Depending on the project and Docker version, the generated files can vary.
Docker Compose file
Inside compose.yaml, we define our services.
For example:
services:
web:
build:
context: .
ports:
- "5173:5173"
volumes:
- .:/app
- /app/node_modulesLet’s understand this.
services
services:
Defines the different services/containers that make up our application.
For example:
services:
web:
backend:
database:
web:
This is the name of our service.
build
build:
context: .
This tells Docker Compose where the Dockerfile and build context are located.
. means: Current directory
ports:
- “5173:5173”
Maps:
Host port 5173 → Container port 5173
volumes
volumes:
- .:/app
- /app/node_modules
The first volume:
.:/app
mounts the current local directory into /app inside the container.
The second:
/app/node_modules
keeps the container’s node_modules separate.
This is useful during development because changes made locally can immediately be reflected inside the container.
Running Docker Compose
Once we have the compose.yaml file, we can start the application using:
docker compose upDocker Compose reads the configuration and creates the required containers.
To run in the background:
docker compose up -d
The -d means detached mode.
To stop the services:
docker compose down







