Containerize Multi-container JavaScript application
Run the Database Server
We will use postgres official image since we only need simple image to demonstrate.
To run this database server image, execute this command:
docker container run \
--detach \
--name=notes-db \
--env POSTGRES_DB=notesdb \
--env POSTGRES_PASSWORD=secret \
--network=notes-api-network \
postgres:12
# a7b287d34d96c8e81a63949c57b83d7c1d71b5660c87f5172f074bd1606196dc
docker container ls
# CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
# a7b287d34d96 postgres:12 "docker-entrypoint.s…" About a minute ago Up About
The --env
option for the container run
and container create
commands can be used for providing environment variables to a container.
But databases like PostgreSQL, MongoDB, and MySQL persist their data in a directory. If container is destroy, we'll lose all our data.
To solve this problem, a named volume can be used.
Work with named volume
The volume create command can be used for creating a named volume.
docker volume create <volume name>
To create a volume named notes-db-data
, we can execute the following command:
docker volume create notes-db-data
# notes-db-data
docker volume ls
# DRIVER VOLUME NAME
# local notes-db-data
To stop and remove the notes-db
container:
docker container stop notes-db
# notes-db
docker container rm notes-db
# notes-db
Now run a new container and assign the volume using the --volume
or -v
option.
docker container run \
--detach \
--volume notes-db-data:/var/lib/postgresql/data \
--name=notes-db \
--env POSTGRES_DB=notesdb \
--env POSTGRES_PASSWORD=secret \
--network=notes-api-network \
postgres:12
# 37755e86d62794ed3e67c19d0cd1eba431e26ab56099b92a3456908c1d346791
Now we can inspect the notes-db
container to make sure that the mounting was successful:
docker container inspect --format='{{range .Mounts}} {{ .Name }} {{end}}' notes-db
# notes-db-data
Access logs from a Container
In order to see the logs from a container, we can use the container logs
command.
docker container logs <container identifier>
To access the logs from the notes-db container, you can execute the following command:
docker container logs notes-db
# The files belonging to this database system will be owned by user "postgres".
# This user must also own the server process.
# The database cluster will be initialized with locale "en_US.utf8".
# The default database encoding has accordingly been set to "UTF8".
# The default text search configuration will be set to "english".
#
# Data page checksums are disabled.
#
# fixing permissions on existing directory /var/lib/postgresql/data ... ok
# creating subdirectories ... ok
# selecting dynamic shared memory implementation ... posix
# selecting default max_connections ... 100
# selecting default shared_buffers ... 128MB
# selecting default time zone ... Etc/UTC
# creating configuration files ... ok
# running bootstrap script ... ok
# performing post-bootstrap initialization ... ok
# syncing data to disk ... ok
#
#
# Success. You can now start the database server using:
#
# pg_ctl -D /var/lib/postgresql/data -l logfile start
#
# initdb: warning: enabling "trust" authentication for local connections
# You can change this by editing pg_hba.conf or using the option -A, or
# --auth-local and --auth-host, the next time you run initdb.
# waiting for server to start....2021-01-25 13:39:21.613 UTC [47] LOG: starting PostgreSQL 12.5 (Debian 12.5-1.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit
# 2021-01-25 13:39:21.621 UTC [47] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
# 2021-01-25 13:39:21.675 UTC [48] LOG: database system was shut down at 2021-01-25 13:39:21 UTC
# 2021-01-25 13:39:21.685 UTC [47] LOG: database system is ready to accept connections
# done
# server started
# CREATE DATABASE
#
#
# /usr/local/bin/docker-entrypoint.sh: ignoring /docker-entrypoint-initdb.d/*
#
# 2021-01-25 13:39:22.008 UTC [47] LOG: received fast shutdown request
# waiting for server to shut down....2021-01-25 13:39:22.015 UTC [47] LOG: aborting any active transactions
# 2021-01-25 13:39:22.017 UTC [47] LOG: background worker "logical replication launcher" (PID 54) exited with exit code 1
# 2021-01-25 13:39:22.017 UTC [49] LOG: shutting down
# 2021-01-25 13:39:22.056 UTC [47] LOG: database system is shut down
# done
# server stopped
#
# PostgreSQL init process complete; ready for start up.
#
# 2021-01-25 13:39:22.135 UTC [1] LOG: starting PostgreSQL 12.5 (Debian 12.5-1.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit
# 2021-01-25 13:39:22.136 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432
# 2021-01-25 13:39:22.136 UTC [1] LOG: listening on IPv6 address "::", port 5432
# 2021-01-25 13:39:22.147 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
# 2021-01-25 13:39:22.177 UTC [75] LOG: database system was shut down at 2021-01-25 13:39:22 UTC
# 2021-01-25 13:39:22.190 UTC [1] LOG: database system is ready to accept connections
The --follow
or -f
option for the command which lets you attach the console to the logs output and get a continuous stream of text.
Create a Network and Attaching the Database Server
First, create a network named notes-api-network
in our system:
docker network create notes-api-network
Now attach the notes-db
container to this network by executing the following command:
docker network connect notes-api-network notes-db
Write the Dockerfile
# stage one
FROM node:lts-alpine as builder
# install dependencies for node-gyp
RUN apk add --no-cache python make g++
WORKDIR /app
COPY ./package.json .
RUN npm install --only=prod
# stage two
FROM node:lts-alpine
EXPOSE 3000
ENV NODE_ENV=production
USER node
RUN mkdir -p /home/node/app
WORKDIR /home/node/app
COPY . .
COPY --from=builder /app/node_modules /home/node/app/node_modules
CMD [ "node", "bin/www" ]
This is a multi-staged build.
The first stage is used for building and installing the dependencies using node-gyp and the second stage is for running the application.
To build an image from this Dockerfile, we can execute the following command:
docker image build --tag notes-api .
# Sending build context to Docker daemon 37.38kB
### LONG INSTALLATION STUFF GOES HERE ###
# Successfully built 9ea100571585
# Successfully tagged notes-api:latest
To make sure the database container is running, and is attached to the notes-api-network
, run:
docker container inspect notes-db
# [
# {
# ...
# "State": {
# "Status": "running",
# "Running": true,
# "Paused": false,
# "Restarting": false,
# "OOMKilled": false,
# "Dead": false,
# "Pid": 11521,
# "ExitCode": 0,
# "Error": "",
# "StartedAt": "2021-01-26T06:55:44.928510218Z",
# "FinishedAt": "2021-01-25T14:19:31.316854657Z"
# },
# ...
# "Mounts": [
# {
# "Type": "volume",
# "Name": "notes-db-data",
# "Source": "/var/lib/docker/volumes/notes-db-data/_data",
# "Destination": "/var/lib/postgresql/data",
# "Driver": "local",
# "Mode": "z",
# "RW": true,
# "Propagation": ""
# }
# ],
# ...
# "NetworkSettings": {
# ...
# "Networks": {
# "bridge": {
# "IPAMConfig": null,
# "Links": null,
# "Aliases": null,
# "NetworkID": "e4c7ce50a5a2a49672155ff498597db336ecc2e3bbb6ee8baeebcf9fcfa0e1ab",
# "EndpointID": "2a2587f8285fa020878dd38bdc630cdfca0d769f76fc143d1b554237ce907371",
# "Gateway": "172.17.0.1",
# "IPAddress": "172.17.0.2",
# "IPPrefixLen": 16,
# "IPv6Gateway": "",
# "GlobalIPv6Address": "",
# "GlobalIPv6PrefixLen": 0,
# "MacAddress": "02:42:ac:11:00:02",
# "DriverOpts": null
# },
# "notes-api-network": {
# "IPAMConfig": {},
# "Links": null,
# "Aliases": [
# "37755e86d627"
# ],
# "NetworkID": "06579ad9f93d59fc3866ac628ed258dfac2ed7bc1a9cd6fe6e67220b15d203ea",
# "EndpointID": "5b8f8718ec9a5ec53e7a13cce3cb540fdf3556fb34242362a8da4cc08d37223c",
# "Gateway": "172.18.0.1",
# "IPAddress": "172.18.0.2",
# "IPPrefixLen": 16,
# "IPv6Gateway": "",
# "GlobalIPv6Address": "",
# "GlobalIPv6PrefixLen": 0,
# "MacAddress": "02:42:ac:12:00:02",
# "DriverOpts": {}
# }
# }
# }
# }
# ]
We can run a new container by executing the following command:
docker container run \
--detach \
--name=notes-api \
--env DB_HOST=notes-db \
--env DB_DATABASE=notesdb \
--env DB_PASSWORD=secret \
--publish=3000:3000 \
--network=notes-api-network \
notes-api
# f9ece420872de99a060b954e3c236cbb1e23d468feffa7fed1e06985d99fb919
To check if the container is running properly or not, you can use the container ls
command:
docker container ls
# CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
# f9ece420872d notes-api "docker-entrypoint.s…" 12 minutes ago Up 12 minutes 0.0.0.0:3000->3000/tcp notes-api
# 37755e86d627 postgres:12 "docker-entrypoint.s…" 17 hours ago Up 14 minutes 5432/tcp notes-db
Execute commands in a running Container
We'll have to run the database migration necessary for setting up the database tables before using this container.
Use the exec
command to execute a custom command inside a running container.
docker container exec <container identifier> <command>
To execute npm run db:migrate inside the notes-api container, you can execute the following command:
docker container exec notes-api npm run db:migrate
# > notes-api@ db:migrate /home/node/app
# > knex migrate:latest
#
# Using environment: production
# Batch 1 run: 1 migrations
When we want to run an interactive command inside a running container, we'll have to use the -it
flag
docker container exec -it notes-api sh
# / # uname -a
# Linux b5b1367d6b31 5.10.9-201.fc33.x86_64 #1 SMP Wed Jan 20 16:56:23 UTC 2021 x86_64 Linux