Sitemap
Sandil Helitha Perera

Sandil Helitha Perera

Docker

7 min readSep 7, 2026

--

Press enter or click to view image in full size

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

Press enter or click to view image in full size
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:

http://localhost:5173

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_modules

Let’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 up

Docker 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

--

--