Optimizing your Docker workflow

Docker

We create a lot of single responsibility services including fetching mail, downloading groups of files, cleaning data, importing data, and many others. This requires us to create new servers that need to be monitored and maintained so we use docker containers to normalize our process and work efficiently. From testing to staging to production, docker containers provide a simplistic way to create disposable server images.

The primary drawback is most docker images lack proper setup or are not designed for your network or architecture. Below are a list of recommendations that will make creating docker containers a less time consuming process.

Separate the Stack

If you are building out multiple docker files because you have a distributed system then you need to analyze all the layers of software your server should incorporate. These “layers” should then be turned into their own docker images so you can stack them when building new servers.

A typical server might have the following layers within its stack

  • Base Image: core utilities, build essential toolkit and core operating system setup
  • Security packages: log analyzers and security configuration
  • Project packages: all the dependencies your project requires like PHP and MySQL
  • Project execution: The execution commands to start the application or server

By separating all these layers into individual images you can efficiently cache each layer and quickly build projects.

Add auditing at the lowest levels

A common thing that is missing in most docker implementations is proper auditing and security. The concept of a disposable container seems enough for most people because if it becomes infected just rebuild the image. However having a security layer is a good idea in the long run and the installation of these utilities is very simple.

A few of our recommendations:

  • Log analyzers for detecting intrusions or software crashes
  • Intrusion detection systems that perform sanity checks on core system files
  • If your service is using a common transfer protocol such as HTTP, FTP, SSH the server should be safe guarded with an automatic IP banning system
  • Anti malware services like ClamAV can also prove very beneficial
  • Enforce iptables and SELinux
  • Enable syslog

With all this said security is only as strong as the weakest link in your code or team. You will want to receive daily reports on what has happened to your system in an area where the entire team can monitor it and be notified. This can be done by either adding a mail service to your stack and sending the emails to a group address, or you can write your log files to a directory that is monitored by a logging client like PaperTrail.

Emulating distributed systems with docker compose

Most systems will have multiple services that run on different servers. The one issue is these services need to communicate with each other and in a development environment that means spinning up each service. When docker first arrived on the scene this was a huge problem because it violated their “1 service 1 container” policy. The reality however is you can easily run more than one service in a single container without a problem. A lot of people opt for this because it simplifies the development stack and greatly reduces the fragility of building out the images.

Samples taken from www.docker.com

# Dockerfile
# Primary Application or Service

FROM python:2.7
WORKDIR /code
ADD requirements.txt /code/
RUN pip install -r requirements.txt
ADD . /code
CMD python app.py

# docker-compose.yml
# YAML file composing all of your services
# web in this case would be your application
# “links” will bind the postgres image to the address “db:8000”

web:
build: .
links:
– db
ports:
– “8000:8000”
db:
image: postgres

Now we can easily build multi-service applications using docker-compose. The way it works is by allowing the developer to specify a YAML file containing services that the core application will depend on and will automatically link the external containers to your application. That means you can simulate network requests to a server as if they are being used on an internal network.

Managing Volumes and avoiding rebuilds

Building images takes time, and this is especially true if you run into a few errors in your Dockerfile along the way. How can we avoid rebuilding when our project changes? Mounting the project code directly is one viable solution and works well in most cases. This allows you to make changes to the code on the host machine and it will immediately reflect on the virtual machine.

This can be particularly helpful if you need to do local development for a project and don’t want the hassle of resending your entire context through a docker build. Although this is generally not recommended for production deployments — where a disposable image is preferred.

One more important thing to note is getting the base images right. Because every time a base image is modified all children of that image have to be rebuilt.

Utilize .dockerignore files to minimize your docker context. If you have a project with a directory containing a bunch of data that the application doesn’t need (eg. debug files, git version history) then you can have the directories or individual files ignored by adding them to the .dockerignore file. By reducing your context the upfront startup time when building your image is reduced.

Wrapping it up

From our discussion you can see a few things are clear when designing containers. First, keep the stack separated so different layers of your server can be reused or modified without changing the entire system. Make sure core services are present and running such as syslog and basic security daemons. Finally when building development images remember to mount your project volumes to avoid rebuilding your image every time a code change occurs.

Categories
Technology