Hello everyone,
In the last post, we discussed the concept of volumes. We discussed the use of named and anonymous volumes.
In this post, we will discuss the need and use of bind mounts
We will learn the use of .dockerignore
files. We will also see working with environment
variables and .env
files. We will also learn the use of build arguments
.
So far using anonymous and named volumes, we have been able to persist temporary app data. However, during the development of our application, new changes made in our source code are not reflected in our app until we rebuild our image. During development, we change a lot of code and hence cannot build again and again.
This is where build mounts are needed and help us. Bind mounds save data in a location on the host file system, not tied to any specific container. Data can be shared across containers.
We could put our source code into a bind mount and make sure our container is aware of that. The source code then is not used from the path in COPY . .
command but from bind mount.
Bind mounts are perfect for persistent and editable data. Data survives container shutdown/restart – removal on the host and can be reused for the same container (across restarts).
To use bind mount follow the following steps:
We already have our image ready, we just need to use it to run the new container. Like named volumes, bind mounts are also added while running the container.
docker run -p 3001:80 -d --rm --name feedback-app -v feedback:/app/feedback -v "/Users/manvisharma/Projects/Docker/Part 2/data-volumes-01-starting-setup":/app feedback-node:volumes
As seen in the command above, we have kept the named volumes since that will persist our application data(i.e., our feedbacks), but for bind mounts, we have added another volume using -v
flag, and the first value before the colon :
, is the absolute path of our application in our system.
The second value is our application inside the container. The absolute path will be different for all of you depending on where you have saved your project. I have mentioned mine in the command.
MacOs : -v $(pwd):/app
Windows: -v "%cd%":/app
preferences/settings< Resources<File Sharing
section.Also, after running the command mentioned above, the container crashes and deletes automatically due to the --rm
flag.
To check the error, just run the container without --rm
flag, and check for docker logs feedback-app
.
The error above occurs because of the bind mount we created. So the new volume we created seems to override everything, even node_modules
. Since the node_modules
are not present in the local folder. This is logical since the local node_modules
can be not updated, or maybe of some old version. This can create inconsistency here.
So the fix here is to add an anonymous volume for node_modules
. The rule of docker is that it prefers the longest clear internal path. So, it will first save node_modules
in the container, and then override the container code with the local code and this is the use of anonymous volumes in Docker along with bind mounts.
docker run -p 3001:80 -d --rm --name feedback-app -v feedback:/app/feedback -v "/Users/manvisharma/Projects/Docker/Part 2/data-volumes-01-starting-setup":/app -v /app/node_modules feedback-node:volumes
OR
docker run -p 3001:80 -d --rm --name feedback-app -v feedback:/app/feedback -v $(pwd):/app -v /app/node_modules feedback-node:volumes
These changes work.
Now, if we make some changes in our HTML files. They are reflected side by side on our application, without having to rebuild the Docker image. This is how build mounts make development easier.
While working with bind mounts above, we made our HTML development easier. But what if I make changes to the server.js
file? That is nodejs
code and is executed during runtime. If you have worked with nodejs
, you might realise, that we need to restart the node server again after making changes, or we can use nodemon
if we do not wish to do it. We will do the same here so that we can work with nodejs
and docker together.
As of now if we make any changes in the server.js
file. E.g. Add a console.log
statement. Then check for docker logs, I cannot find any logs here. Again code is updated in our container because of the bind mount we created, but the node server is not updated. I will fix this by adding nodemon
package. We do not have to install anything, rather just update package.json
file and CMD options in Docker images. Then rebuild the Docker image and run the container with volumes.
package.json
dependencies
Dockerfile
FROM node
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
EXPOSE 80
CMD [ "npm", "start" ]
On integrating nodemon
, the server changes are also reflected:
If we check our code in the server.js
file, first it copies our feedback to temp
folder and check if the feedback already exists. If it exists, it does not save feedback, and if it does not exist, it saves our feedback to the feedback
folder.
This computation now happens inside our container. If we create an anonymous volume for our temp folder, all computation will be handled locally rather than inside the docker container. This is how we can use anonymous volumes for optimization.
docker run -p 3001:80 -d --rm --name feedback-app -v feedback:/app/feedback -v $(pwd):/app -v /app/node_modules -v /app/temp feedback-node:volumes
By default, all volumes are read-write. The bind that we have created should not be updated by the container. So this volume can be made read-only. We can add an ro
(read-only) option, with the bind mount to do this.
docker run -p 3001:80 -d --rm --name feedback-app -v feedback:/app/feedback -v $(pwd):/app:ro -v /app/node_modules -v /app/temp feedback-node:volumes
docker volume create VOLUME_NAME
Since our bind mounts make the entire application code available to our container, we might not need COPY . .
command in our Dockerfile. If I try to do this, it works, we do not get any issues. But we should completely avoid such practices. This is because, during deployment, we will not create our container using bind mounts. You will realise this when we will explore deployment. We might store things somewhere else.
We will prefer to have a snapshot of our code.
We can add "to-be-ignored" files and folders in our this file. This works just like a .gitignore
file. In general, we want to add anything that isn't required by our application to execute correctly.
For example, Add the following entries:
1. Dockerfile
2. .git
3. node_modules
This would ignore the Dockerfile
as well as a potentially existing .git
 folder (if we are using Git in our project).
We do not need to copy everything as specified by COPY. .
. We can make a .dockerignore
file and add node_modules
here, so that if there are any node_modules
locally they do not override the ones present in the container, as the ones present locally can be outdated.
Docker supports build-time ARGuments and runtime ENVironment variables.
-Available inside of Dockerfile & in application code
-Set via ENV
in Dockerfile or via --env
on docker run
If you have worked in Node, you might know that Node embraces the concept of environment variables.
We add these variables in Dockerfile
.
FROM node
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
ENV PORT 80
EXPOSE $PORT
CMD [ "npm", "start" ]
We can also customise it further using the docker run command while running container
docker run -p 3001:8000 --env PORT=8000 -d --rm --name feedback-app feedback-node
So PORT 80 in Dockerfile is just a default value. Changing this with docker run updates our value. We have to update port with -p
flag accordingly.
We can also use -e
flag instead of --env
flag. We can specify multiple variables like
-e PORT=8000 -e.... so on
We can also use a dedicated env file.
docker run -p 3001:80 --env-file ./.env -d --rm --name feedback-app feedback-node
-Available inside of Dockerfile
-NOT accessible in CMD or any application code
-Set on image build(docker build) via --build-arg
We can also add build-time arguments in Dockerfile.
FROM node
WORKDIR /app
ARG DEFAULT_PORT=80
COPY package.json .
RUN npm install
COPY . .
ENV PORT $DEFAULT_PORT
EXPOSE $PORT
CMD [ "npm", "start" ]
We can now customise this while building the Docker image.
docker build -t feedback-node:dev --build -arg DEFAULT_PORT=8000
So again 80 is the default value, but we can give our own custom value.
That's all about Bind mounts and other related concepts. We will continue in the next post.
Docker: Managing data with Volumes (Part 1) Hello everyone, In the last post, we learned to perform various operations with containers and images. In this post, we will explore volumes, which are like state management for running applications with docker. We will discuss different kinds of data we encounter while working with docker. We will learn about different kinds of volumes, and explore anonymous and named volumes in detail by discussing their implementation in a node server application. We will create these volumes in our system.
Docker: Basic Operations with Containers and Images Hello everyone, In the last post, we learned how to create and restart containers in Docker using a Python app example. We also learned about different modes in which containers can run. In this post, we will cover the commands used for deleting containers and images, the validation process required for deleting them, and the importance of tagging images. Additionally, we will discuss renaming containers and the advantages of tagging images. We will learn to inspect images and copy files to and from containers. We will explore sharing images with other users/developers, and some other basic related actions.