Re:GitHub Actions could be so much better
by ryan29 on Friday September 22, 2023 @02:24PM
(#37615718)
I skew heavily to #2. The vendor lock-in makes me reluctant to use most CI systems. I don't do much CI stuff, but I ended up using Drone.io for some Docker builds because I was able to set it up a lot differently than GitHub Actions, GitLab CI, etc..
Most of the CI systems have some type of plug-in for Docker builds. Drone has a Docker plugin, but I don't use it. If you look at all of them, you'll notice that you end up with a single build step that does everything Docker related; build, tag, push.
What if you want to add something between build and push? It's difficult (or impossible) and you start to end up with one gigantic, do-everything build step. Just having a flag and a conditional in the build step to enable/disable pushing bugged me because 'push' is a distinct command with Docker, so I think it makes more sense if 'push' is a distinct build step. Nothing supports that though and you seem to end up with the same "do-everything" build step on every platform.. The vendor lock-in caused by #1 doesn't even matter at that point because your build logic is married to the the underlying Docker build plugin of the CI system.
Instead, I went for something that does a better job of mirroring what you get on the CLI when you run Docker by hand. I wanted distinct build steps for build, test, tag, push. That's what I do on my dev machine, so, in my opinion, that's what should be happening in CI. That way I can debug by manually running each step locally, just like I would without a CI system.
What I do with Drone is ignore their plugin and start a detached process to provide a Docker daemon. The default is to start a Docker-in-Docker daemon, but, if I'm running locally, I can pass --instance 'docker.localhost' and it starts a Caddy server that proxies to the local Docker socket instead of running a Docker-in-Docker container.
Every build step after that can access the Docker daemon. It's scoped to the pipeline.
My 'build' step is pretty much 'docker compose build --pull', so I can do the same on the CLI to debug my Docker build without touching the CI system. If I want to use the CI system I can add that '--instance' flag to bind to the local daemon which is quick and takes advantage of caching. I can also do a default build locally which spins up a Docker-in-Docker container. That mimics what the CI agent will be doing. Finally I can push commits and let it run on the CI system.
Every build step after the setup uses the 'docker.io/docker' image, so the small number of scripts it takes to get everything set up are executing in a fairly well known environment.
I purposely avoid caching by default. I think it adds too much complexity. I use Nexus as a local cache for all artifacts (Docker, APT, etc.) which is an ok compromise and ensures I'm not hitting the upstream services more than needed.
If someone took Drone away from me tomorrow I could do my builds from the CLI without even thinking about it. I think it's a good habit to get into to ask yourself what would happen if you lose a tool before getting too committed to it.
Most of the CI systems have some type of plug-in for Docker builds. Drone has a Docker plugin, but I don't use it. If you look at all of them, you'll notice that you end up with a single build step that does everything Docker related; build, tag, push.
What if you want to add something between build and push? It's difficult (or impossible) and you start to end up with one gigantic, do-everything build step. Just having a flag and a conditional in the build step to enable/disable pushing bugged me because 'push' is a distinct command with Docker, so I think it makes more sense if 'push' is a distinct build step. Nothing supports that though and you seem to end up with the same "do-everything" build step on every platform.. The vendor lock-in caused by #1 doesn't even matter at that point because your build logic is married to the the underlying Docker build plugin of the CI system.
Instead, I went for something that does a better job of mirroring what you get on the CLI when you run Docker by hand. I wanted distinct build steps for build, test, tag, push. That's what I do on my dev machine, so, in my opinion, that's what should be happening in CI. That way I can debug by manually running each step locally, just like I would without a CI system.
What I do with Drone is ignore their plugin and start a detached process to provide a Docker daemon. The default is to start a Docker-in-Docker daemon, but, if I'm running locally, I can pass --instance 'docker.localhost' and it starts a Caddy server that proxies to the local Docker socket instead of running a Docker-in-Docker container.
Every build step after that can access the Docker daemon. It's scoped to the pipeline.
My 'build' step is pretty much 'docker compose build --pull', so I can do the same on the CLI to debug my Docker build without touching the CI system. If I want to use the CI system I can add that '--instance' flag to bind to the local daemon which is quick and takes advantage of caching. I can also do a default build locally which spins up a Docker-in-Docker container. That mimics what the CI agent will be doing. Finally I can push commits and let it run on the CI system.
Every build step after the setup uses the 'docker.io/docker' image, so the small number of scripts it takes to get everything set up are executing in a fairly well known environment.
I purposely avoid caching by default. I think it adds too much complexity. I use Nexus as a local cache for all artifacts (Docker, APT, etc.) which is an ok compromise and ensures I'm not hitting the upstream services more than needed.
If someone took Drone away from me tomorrow I could do my builds from the CLI without even thinking about it. I think it's a good habit to get into to ask yourself what would happen if you lose a tool before getting too committed to it.