A couple of weeks ago I did an overall upgrade of my server and moved this jekyll blog from Heroku’s servers over to my own dedicated machine. During this process I also gave my blog an overhaul and added SSL encryption and made it more efficient by minimizing more resources and adhering to Google’s PageSpeed Best Practices.
Simpliyfing Deployments with Docker
The new setup is based on the awesome dokku, a docker-powered Platform as a Service (PaaS). Coming from the heroku platform it now allows me to quickly deploy changes of my webpage and host it on my own machine. Furthermore I can run multiple different applications that are isolated from each other (anagram, screen, sudoku). But Docker can provide even more benefits:
- Rapid application deployment – containers include the minimal runtime requirements of the application, reducing their size and allowing them to be deployed quickly.
- Portability across machines – an application and all its dependencies can be bundled into a single container that is independent from the host version of Linux kernel, platform distribution, or deployment model. This container can be transfered to another machine that runs Docker, and executed there without compatibility issues.
- Version control and component reuse – you can track successive versions of a container, inspect differences, or roll-back to previous versions. Containers reuse components from the preceding layers, which makes them noticeably lightweight.
- Sharing – you can use a remote repository to share your container with others. Red Hat provides a registry for this purpose, and it is also possible to configure your own private repository.
- Lightweight footprint and minimal overhead – Docker images are typically very small, which facilitates rapid delivery and reduces the time to deploy new application containers.
- Simplified maintenance – Docker reduces effort and risk of problems with application dependencies.
To setup an application with dokku I just had to create a simple Dockerfile. It is based of the official ruby image and adds node and yarn to the mix.
Afterwards I install all dependencies through yarn, gem and bower.
Finally I can build the site with the jekyll command.
Docker Caching Issues
One major issue I encountered was that changes often took a while to be pushed. Each time I change any small file, e.g. correct a typo on a blog post, docker would reinstall the entire blog with its dependencies. Usually Docker is smart about this and keeps a cache of the different parts of the Dockerfile. But if you added something to the docker context and something changes docker has to restart from there. Since I added my complete project folder at once, any change, as small as it might be, always triggered a complete rebuild of my application.
Dependency Jungle
The steps of the process that took the most time where the dependency installations. For my blog I use three different commands that take a longer time to execute:
- The ruby gem command
- The node.js yarn package manager
- The bower tool for html components
Ideally I still would like to test my blog under the same conditions as on the server. If I would have to reinstall all dependencies everytim I make a minor change that would slow me down drastically. So what do we do about this?
Solution
To ensure that dependencies are only reinstalled when the corresponding configuration changes we have to selectively add the files to the docker image.
For my blog these files are:
- Gemfile (Ruby)
- package.json (Node.js)
- bower.json (bower)
Dependency Files
We have to add these 3 files beforehand. And furthermore install the dependencies in a global or temporary location so that we can add them back to the rest of the application later.
Gemfile
package.json
bower.json
Ordering
Depending on which configuration file changes, all the steps that come afterwards have to be executed again. If we want to be smart about our approach, we can order the different installation steps in a more efficient way. In my case this would be:
- yarn
- gem
- bower
For my application bower dependencies are changed the most frequent, so I install it last. After that I sometimes install new jekyll plugins. And finally comes yarn, where there are mostly just development requirements like gulp that almost never change and I therefore install it right at the beginning.
Result
Final Words
Of course you can use a similar setup for all kinds of dependencies and installation steps that you have. The core idea is to selectively add the files and build your docker image on a step-by-step basis.
Below you can find the final version of my Dockerfile. Don’t hesitate to ask, if you have any questions!