Following part 1, here is a protip that will significantly boost your build time of your brand new dockerized dev environment.

1. What, it wasn't perfect already ?

In part 1 we ended up with the following Dockerfile:

Now let's have a look at what happens when you install a new package (in this example this corresponds to changing the package.json file). We're going to install the 'twit' package. Because why not. If you understood the part 1, then you probably figured out that you have to run the npm install command from within the container (since there is no such thing as npm on our computer, is it?):

docker-compose run hello npm install --save twit

a git status will show that the package.json has changed. We now have to rebuild our images with

docker-compose build

Which will trigger a fresh install.

But there is a catch : npm install is run from scratch. For a medium to big sized project, this can really take a long time. And so does gem install for rubyists.

Furthermore, it will also take long for the CI server to build your image and your tests won't be as fast as you would like them to be.

This can definitely be improved.

2. Make it faster !

If you take a closer look at our Dockerfile, then you'll see everything starts with a FROM:mhart/alpine-node command.

This basically means that we start with a docker image that already contains node, and some basic unix tools (try it out, you can run commands such as ls, echo, pwd…etc although we never specifically installed them).

=> What if we build our own image, that would already contain some node_modules packages ?

I'm glad you asked. Let's do this this way :

Build an image that contains node + a pre-installation of our current libraries

Push this image to docker-hub so it's available for everyone (or for the team & CI servers if you do use a private image)

Use this image in the FROM instruction of our Dockerfile

Build our current project by taking advantage of the pre-installed libraries

Enjoy lightning-fast builds !

Let's roll back and do it the right way.

A complete working directory can be found here:

git clone --branch faster https://github.com/aherve/simple-dockerized-dev-env

First of all, let's prepare a base-package.json. At this point you should simply use your current package.json file. It means everything listed under this package.json file are going to be installed in your bootstrap image. If you decide to change it further, then only the additional packages will have to be installed by the docker-compose build command.

With this we can build our bootstrap image, that we describe in the Dockerfile.bootstrap file:

Now that you are beginning to get familiar with the Dockerfiles, you can see that we are simply taking our base-package.json, and installing the dependencies in the /tmp/node_modules directory of the container.

At this point you should build this image, and send it to docker-hub so it becomes available under your-login/my-app-bootstrap.

For the purpose of the demonstration, let's simply build the image locally and prove it is working:

docker build -t bootstrap_demo -f Dockerfile.bootstrap .

Alright, it built. We don't really care about how much time it takes, we are only going to build it once for all.

Next, let's modify our main Dockerfile as follows:

Build our brand new image:

docker-compose build

Did you notice how npm install did not have anything to do here ? Definitely faster than re-installing all your dependencies !

Does it work ? (spoiler: it does)

docker-compose run test

count

✓ should return 1 1 passing (10ms)

And now the final : let's change our package.json and see what happens :

docker-compose run hello npm install --save twit

docker-compose build

And *Voilà*, we successfully installed the twit package in no-time !

You can check that it worked:

docker-compose run hello npm list | grep twit

> `-- twit@2.2.3

docker-compose run test

hello is loaded ! count

✓ should return 1

1 passing (9ms)

Now when you trigger a fresh build that uses your Dockerfile, only twit will have to be reinstalled.

When you are tired to install it, simply add it to the base-package.json, build a new bootstrap image that you tag with a different version, and tell your Dockerfile to use it.

This is actually simply a docker implementation of what you are used to do everyday : When you run npm install, it it quite fast, unless you previously removed your node_modules. In this case, it has to rebuild everything from scratch.

This trick is just as good for ruby developers, since package.json is strictly equivalent to a Gemfile

Conclusion:

This concludes our tutorial. As you can see, it is not that difficult to build a fast, light, and efficient development environment using docker. We did not have to compromise any of the features we previously had (like hot-reloading, automated-testing…etc), but we got in addition to this :

standardized environments

one-click install

reproductible CI-test on any computer

Although we did not speak about production deployment here, you can easily imagine that it is quite straightforward to push the build container from the CI/CD server to some docker-hosting platform.

At Hunteed we set up a workflow using codeship that consist of

develop something, and test it locally push to github webhook is triggered and codeship test it the same way if codeship succeeded to build the image, and if the image proved it can pass the testing-suite, then push this very image to docker-hub Either set a docker-hub trigger, or trigger a redeployment of your image on some docker-hosting platform. Your code is up to date and in production.

With such a workflow, and together with proper code coverage, it becomes quite straightforward to deploy several times a week.

Hope this helped,

If you want to see a more real-life environment, you can have a look at this dockerized typescript-node-express build

Happy coding!