Docker is an environment to run small isolated virtual machines called containers.
Container are stateless, which means they don't store the state of the application or data created during the runtime. Any changes made to filesystem within a container are temporary and will be lost forever if you stop, restart or remove container! Stateless design ensures that containers are lightweight, portable, and self-sufficient and you can start or move between servers without affecting the application's functionality or causing any discrepancies in data.
Container should be run from a snapshot of working application with all the environment, dependencies and application itself included in it. This snapshot has name of docker image. Script containing steps required to build a Docker container image called Dockerfile. The script might operate a very limited set of commands.
To build a Docker container image from a Dockerfile, you use the docker build
command followed by a build context (usually a local directory containing the Dockerfile). Once the image is built, you can run containers from it using the docker run
command.
Lets create a basic Docker image with NodeJS. First create a file called Dockerfile
(without extension). Open it with any text editor (I always recommend VSCode) and place the following:
# Use the latest official Node.js runtime as a parent image
FROM node:latest
# Set the working directory to /app
WORKDIR /app
# Copy the package.json file into the container at /app
COPY package.json /app
# Install any needed packages specified in package.json
RUN npm install
# Copy the rest of the application code into the container at /app
COPY . /app
# Make port 3000 available to the world outside this container
EXPOSE 3000
# Run app.js when the container launches
CMD ["node", "app.js"]
Save the file and try to build the image:
docker build -t your-image-name .
You should have an error:
docker build -t your-image-name .
[+] Building 1.7s (8/10)
=> [internal] load build definition from Dockerfile
=> [internal] load metadata for docker.io/library/node:latest
=> [auth] library/node:pull token for registry-1.docker.io
=> [internal] load build context
=> => transferring context: 539B
=> [1/5] FROM docker.io/library/node:latest@sha256:242d81ad2a91353ac3a5ed3598582acb4a9a7761b16c60524b949a1603707848
=> => sha256:4e271ab266a4b76874c8e7c8aa431a8dbe636af19fa5e76403e3acbd9b8d3450 2.21kB / 2.21kB
=> CACHED [2/5] WORKDIR /app
=> ERROR [3/5] COPY package.json /app
> [3/5] COPY package.json /app:
failed to compute cache key: "/package.json" not found: not found
This is because we don't have package.json
file in our project.
Let's create missing files:
{
"name": "node-app",
"version": "1.0.0",
"description": "A simple Node.js app",
"main": "app.js",
"scripts": {
"start": "node app.js"
},
"dependencies": {
"express": "^4.17.1"
}
}
const express = require('express');
const app = express();
const port = 3000;
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(port, () => {
console.log(`Node app listening at http://localhost:${port}`);
});
Now try to build the image again:
docker build -t your-image-name .
You should see something like this:
$ docker build -t your-image-name .
[+] Building 53.0s (11/11) FINISHED
=> [internal] load build definition from Dockerfile
=> => transferring dockerfile: 545B
=> [internal] load .dockerignore
=> => transferring context: 2B
=> [internal] load metadata for docker.io/library/node:latest
=> [auth] library/node:pull token for registry-1.docker.io
=> [1/5] FROM docker.io/library/node:latest@sha256:d9fd72fa4cc310fd57cf5febd50be1304375b4f7b2945784127cad85c7cd1100
=> => resolve docker.io/library/node:latest@sha256:d9fd72fa4cc310fd57cf5febd50be1304375b4f7b2945784127cad85c7cd1100
=> => sha256:d9fd72fa4cc310fd57cf5febd50be1304375b4f7b2945784127cad85c7cd1100 1.21kB / 1.21kB
=> => sha256:7dd206bea61ff3e3b54be1c20b58d8475ddd6f89df176146ddb7a2fd2c747ea2 24.03MB / 24.03MB
=> => sha256:980cf5b26237081e7e05515f385cd8eb270cf41fd13538a146c473040b10abd8 2.00kB / 2.00kB
=> => sha256:250e9c100ea2ad5745b8c69edef0c3dcf9b2cbb055fb7dbd4fe7fb3f9fa45bae 7.24kB / 7.24kB
=> => sha256:d52e4f012db158bb7c0fe215b98af1facaddcbaee530efd69b1bae07d597b711 49.55MB / 49.55MB
=> => sha256:2320f9be4a9c605d1ac847cf67cec42b91484a7cf7c94996417a0c7c316deadc 64.11MB / 64.11MB
=> => sha256:6e5565e0ba8dfce32b9049f21ceeb212946e0bb810d94cbd2db94ca61082f657 211.00MB / 211.00MB
=> => sha256:5f1526a28cf91707a0af45c5624979f67215b1330efd66c05a0f94839c96f0bc 3.37kB / 3.37kB
=> => extracting sha256:d52e4f012db158bb7c0fe215b98af1facaddcbaee530efd69b1bae07d597b711
=> => sha256:d89b7e3d6e3496e51a2a10dfea2cbf36ba8501b95bb374489b955e39e4d9ffdc 47.52MB / 47.52MB
=> => extracting sha256:7dd206bea61ff3e3b54be1c20b58d8475ddd6f89df176146ddb7a2fd2c747ea2
=> => extracting sha256:2320f9be4a9c605d1ac847cf67cec42b91484a7cf7c94996417a0c7c316deadc
=> => sha256:8831f71a931426419aadda667fd199559851d94658edf73c637801f675fbc463 2.27MB / 2.27MB
=> => sha256:beef33e6bd26ef8d9ecfb29760a482aab3272d855db9e4d0c0fd4131d54ef827 449B / 449B
=> => extracting sha256:6e5565e0ba8dfce32b9049f21ceeb212946e0bb810d94cbd2db94ca61082f657
=> => extracting sha256:5f1526a28cf91707a0af45c5624979f67215b1330efd66c05a0f94839c96f0bc
=> => extracting sha256:d89b7e3d6e3496e51a2a10dfea2cbf36ba8501b95bb374489b955e39e4d9ffdc
=> => extracting sha256:8831f71a931426419aadda667fd199559851d94658edf73c637801f675fbc463
=> => extracting sha256:beef33e6bd26ef8d9ecfb29760a482aab3272d855db9e4d0c0fd4131d54ef827
=> [internal] load build context
=> => transferring context: 1.08kB
=> [2/5] WORKDIR /app
=> [3/5] COPY package.json /app
=> [4/5] RUN npm install
=> [5/5] COPY . /app
=> exporting to image
=> => exporting layers
=> => writing image sha256:b2e188b4e3b71679c15312e4aa16224c0e1af7881b9f9e07e8b58a6dc228c9d0
=> => naming to docker.io/library/your-image-name
This app.js file creates a simple Express web application with a single route that returns a "Hello World!" message. The application listens on port 3000.
Now that you have a Docker image, you can run it as a container. To do this, you use the docker run
command. The -p
option maps port 3000 in the container to port 3000 on the host system. The -d
option runs the container in detached mode, leaving the container running in the background. The --name
option assigns a name to the container. The name is used in subsequent commands to refer to the container.
docker run -p 3000:3000 -d --name my-app your-image-name
You can see the running container by using the docker ps
command:
docker ps
If you visit http://localhost:3000 in a web browser, you should see the "Hello World!" message.
You can see the logs from the container by using the docker logs
command:
docker logs my-app
You can stop the container by using the docker stop
command:
docker stop my-app
You can remove the container by using the docker rm
command:
docker rm my-app
You can remove the image by using the docker rmi
command:
docker rmi your-image-name
In this article, you learned how to create a Docker image from a Dockerfile and run a container from it. You also learned how to stop and remove the container and image.