A container is a running instance of an image. You can create, start, stop, move, or delete a container using the Docker API or CLI. You can connect a container to one or more networks, attach storage to it, or even create a new image based on its current state. Basically, a container is a very lightweight VM that only provides the resources and environment to run applications.
-
FROM. The first instruction in the Dockerfile must beFROM, which defines the base image to use to start the build process. TheFROMinstruction can appear multiple times within a single Dockerfile in order to create multiple images. Simply make a note of the last image ID output by the commit before each newFROMinstruction. -
WORKDIR. TheWORKDIRinstruction sets the working directory for anyRUN,CMD,ENTRYPOINT,COPYandADDinstructions that follow it in the Dockerfile. If theWORKDIRdoesn’t exist, it will be created even if it’s not used in any subsequent Dockerfile instruction. -
COPY. TheCOPYinstruction copies new files or directories from<src>and adds them to the filesystem of the container at the path<dest>. -
RUN. TheRUNinstruction will execute any commands in a new layer on top of the current image and commit the results. The resulting committed image will be used for the next step in the Dockerfile. -
CMD. TheCMDinstruction has three forms:CMD ["executable","param1","param2"](exec form, this is the preferred form)CMD ["param1","param2"](as default parameters to ENTRYPOINT)CMD command param1 param2(shell form)
There can only be one
CMDinstruction in a Dockerfile. If you list more than oneCMDthen only the lastCMDwill take effect. -
EXPOSE. TheEXPOSEinstruction informs Docker that the container listens on the specified network ports at runtime. You can specify whether the port listens on TCP or UDP, and the default is TCP if the protocol is not specified.
-
docker run -dp 127.0.0.1:3000:3000. It contains three flags:- The -d flag (short for --detach) runs the container in the background.
- The -p flag (short for --publish) creates a port mapping between the host and the container.
- The -p flag takes a string value in the format of HOST:CONTAINER, where HOST is the address on the host, and CONTAINER is the port on the container. The command publishes the container's port 3000 to 127.0.0.1:3000 (localhost:3000) on the host. Without the port mapping, you wouldn't be able to access the application from the host.
docker stop <container_id>. Stop the container.docker rm <container_id>. Remove the container.docker build -t <image_name> .. Build the image.docker run -dp 3000:3000 <image_name>. Run the container.docker image prune. Remove unused images.docker container prune. Remove all stopped containers.
- Login to Docker Hub:
docker login. - Create a repository on Docker Hub.
- Make sure the visibility to the public.
- Create the repository on Docker Hub.
- Push the image to Docker Hub:
docker push <username>/<docker-image>. If there's no local image that matches the repository name. You need to tag the image first:docker tag <image> <username>/<docker-image>. - Pull the image from Docker Hub:
docker pull <username>/<docker-image>. - Run the image:
docker run -dp 0.0.0.0:3000:3000 <username>/<docker-image>. Binding port to 0.0.0.0 allows you to expose the port to the outside world not just to docker host. By default, the port is using 0.0.0.0 if host is omitted.
There are two ways to persist the DB:
-
Volume mount
Volume mount is a way to persist the data by mounting a directory from the host inside the container. The data will be stored in the host machine. The container will read and write the data from the host machine.
You don't need to create the directory on the host machine. Docker will create it for you and manage it for you.
Example command:
docker run -dp 127.0.0.1:3000:3000 --mount type=volume,src=todo-db,target=/etc/todos getting-started
The
--mountflag using thetype=volumeoption mounts a named volume calledtodo-dbinto the/etc/todosdirectory inside the container. -
Bind mount
Bind mount is a way to persist the data by mounting a directory from the host inside the container. The data will be stored in the host machine. The container will read and write the data from the host machine.
You need to create the directory on the host machine. Docker will not create it for you.
Example command:
docker run -it --mount "type=bind,src=$pwd,target=/src" ubuntu bashThe
--itflag is used to run the container in interactive mode. Theubuntuis the image name. Thebashis the command to run inside the container.The
--mountflag using thetype=bindoption mounts the current directory, represented by thepwdvariable, into the/srcdirectory inside the container.An use case for development is to mount the source code directory into the container. So that you can edit the source code on the host machine and the changes will be reflected inside the container.
Example command:
docker run -dp 127.0.0.1:3000:3000 ` -w /app --mount "type=bind,src=$pwd,target=/app" ` node:18-alpine ` sh -c "yarn install && yarn run dev"
The
--mountflag using thetype=bindoption mounts the current directory, represented by thepwdvariable, into the/appdirectory inside the container.The
-wflag sets the working directory inside the container to/app. So you don't need tocdinto the directory.The
node:18-alpineis the image name. Thesh -c "yarn install && yarn run dev"is the command to run inside the container. -
Multi container apps
By default, Docker containers are isolated from each other. They can't communicate with each other. To allow them to communicate with each other, you need to create a network and attach the containers to the network.
Creating a network:
docker network create docker-uwu
Example adding mysql container and attach it to the network:
docker run -d ` --network docker-uwu --network-alias mysql ` -v todo-mysql-data:/var/lib/mysql ` -e MYSQL_ROOT_PASSWORD=secret ` -e MYSQL_DATABASE=todos ` mysql:8.0
The
--networkflag attaches the container to the network. The--network-aliasflag sets the alias for the container. The-vflag creates a named volume calledtodo-mysql-dataand mounts it to the/var/lib/mysqldirectory inside the container. The-eflag sets the environment variable.Confirm if the database is running:
docker exec -it <container_id> mysql -p
You can then troubleshoot the container networking with nicolaka/netshoot image:
docker run -it --network docker-uwu nicolaka/netshoot
You can look up the IP address of the mysql container:
dig mysql
This works because we set the
--network-aliasflag tomysqlearlier. Basically, the--network-aliasflag sets the hostname of the container.Run container with the network:
docker run -dp 127.0.0.1:3000:3000 ` -w /app -v "$(pwd):/app" ` --name emilia ` --network docker-uwu ` -e MYSQL_HOST=mysql ` -e MYSQL_USER=root ` -e MYSQL_PASSWORD=secret ` -e MYSQL_DB=emilia ` node:18-alpine ` sh -c "yarn install && yarn run dev"
Docker Compose is a tool for defining and running multi-container Docker applications. With Compose, you use a YAML file to configure your application's services. Then, with a single command, you create and start all the services from your configuration.
With it you also can automatically create a network as well as volume and attach the containers to the network.
Example docker-compose.yml:
services:
app:
image: node:18-alpine
command: sh -c "yarn install && yarn run dev"
ports:
- 127.0.0.1:3000:3000
working_dir: /app
volumes:
- ./:/app
environment:
MYSQL_HOST: mysql
MYSQL_USER: root
MYSQL_PASSWORD: secret
MYSQL_DB: emilia
mysql:
image: mysql:8.0
volumes:
- todo-mysql-data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: emilia
volumes:
todo-mysql-data:The services section defines the containers. The app and mysql are the container names. The image is the image name. The command is the command to run inside the container. The ports is the port mapping. The working_dir is the working directory inside the container. The volumes is the volume mount. The environment is the environment variables.
When you add names below the services section, it will create a network alias for the container. So you don't need to set the --network-alias flag.
We need to define the 'volumes' on the root level when using docker compose. So that we can use the named volume in the volumes section. It won't create the volume if we don't define it, unless you run manually. If using volume bind, you don't need to define it.
Run the docker compose:
docker compose up -dThe -d flag runs the container in the background.
Tear down the docker compose:
docker compose down-
Use
.dockerignorefile to ignore files that are not needed in the image. Useful for omitting thenode_modulesdirectory. -
Always make sure to cache the dependencies. So that it won't install the dependencies every time you build the image when you change the source code. Example:
FROM node:18-alpine WORKDIR /app COPY package.json yarn.lock ./ RUN yarn install --production COPY . .
Notice that we copy the
package.jsonandyarn.lockfirst before runningyarn install. So that it will install the cached dependencies. Then we copy the source code. Note that we already omit thenode_modulesdirectory in the.dockerignorefile, so it won't copy thenode_modulesdirectory.This way, when you change the source code, it will only copy the source code and not install the dependencies again. Thus caching the dependencies.
-
Multi-stage builds. To reduce the size of the image, by separating the build stage and the production stage.
Example when building the react static website, you only need node to build the website. But you don't need node to run the website. So you can separate the build stage and the production stage.
# Build stage FROM node:18-alpine AS build WORKDIR /app COPY package* yarn.lock ./ RUN yarn install --production COPY public ./public COPY src ./src RUN yarn run build # Production stage FROM nginx:alpine COPY --from=build /app/build /usr/share/nginx/html
In this example, you use one stage (called build) to perform the actual build using node. In the second stage (starting at FROM nginx), you copy in files from the build stage. The final image is only the last stage being created.