Tyler Smith

Docker, Docker-in-Docker and disappointment

The past few days have been frustrating. I'm trying to get Jenkins set up in a container and I have no idea what I'm doing.

Over the weekend I completed coding the new version of SacMusic, which felt great. But rewriting the app in a new stack wasn't my main goal: I wanted to learn how to build production images in CI/CD pipelines and deploy them using Jenkins. I'm forcing myself to get this pipeline nonsense working before I move onto literally anything else. I'm feeling overwhelmed but trying to stay focused.

Installing Jenkins

I've been following the official Docker installation guide from the Jenkins website for the past couple of days. Docker is literally the first approach under "Installing Jenkins," so it seems like it's the recommended approach.

Unfortunately the Jenkins Docker installation guide leaves a lot to be desired. It walks you through the process of imperatively creating a network from the Docker CLI. You then imperatively create a Docker-in-Docker container by passing nearly a dozen arguments into the Docker CLI. Then you create a Dockerfile for a Jenkins container, and imperatively create a container through Docker's CLI, again passing in nearly a dozen arguments so Jenkins and Docker-in-Docker can talk through the network that you created in the first step.

This process seems nonsensical: Docker Compose is a declarative format specifically for orchestrating containers and networks. Why would the docs recommend setting all of this up imperatively? Why wouldn't they just have a Dockerfile and docker-compose.yml in an easy-to-clone GitHub repo?

I decided to convert this imperative CLI soup into a compose file. If nothing else, it was a learning experience. Here are some of the things I learned along the way:

Wait, what? Using Docker-in-Docker with CIs is a bad idea? It's literally recommended in the Jenkins docs!

Docker-in-Docker considered harmful

According to Docker engineer Jérôme Petazzoni's blog post, using Docker-in-Docker can cause issues with Linux security systems; lead to headaches with conflicting filesystems; and cause build cache misses. Petazzoni instead recommends connecting CI systems to the host Docker instance using a Unix socket, which avoid the issues that come from using Docker-in-Docker. To me, this also feels less janky, though I don't understand enough about these technologies to articulate why.

Advantages of sockets

It hadn't occurred to me that I could use sockets across containers with Docker. A quick search on the subject showed me that connecting Nginx to a container via socket instead of HTTP could improve performance by 40-45%. The same article said that connecting via host network mode would give me similar results, but this mode is only available on Linux hosts. Another article said that bypassing the network stack makes sockets faster, and it also suggested that sockets are more secure because they are subject to system file permissions while TCP is not. I'll likely start favoring sockets for Nginx on future Docker projects.

I'm essentially back at square one with standing up Jenkins in a container, but I learned a lot along the way. Hopefully it goes better tomorrow.