Skip to content
This repository was archived by the owner on May 27, 2025. It is now read-only.

Commit 9c99b92

Browse files
committed
vault backup: 2025-02-02 10:53:30
1 parent 5e8d243 commit 9c99b92

File tree

2 files changed

+138
-56
lines changed

2 files changed

+138
-56
lines changed
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
---
2+
title: Using `just` to properly tag and isolate docker compose projects
3+
tags:
4+
- observations
5+
---
6+
You're developing a dockerized application, so you have a docker compose stack.
7+
8+
```yaml
9+
services:
10+
frontend:
11+
image: 'frontend:develop'
12+
build:
13+
context: ./frontend
14+
backend:
15+
image: 'backend:develop'
16+
build:
17+
context: ./backend
18+
```
19+
20+
This is pretty neat, but it does mean that all your images are built with the tag `develop`. Let's say you switch to another branch, and you want to test the application there. You are _forced_ to rebuild, possibly `docker compose down -v` and lose your old image and state entirely if you want to switch back.
21+
22+
Instead, we can define the environment variable `TAG`, and then rewrite the docker compose to be:
23+
24+
```yaml
25+
services:
26+
frontend:
27+
image: frontend:${TAG:-develop}
28+
# alternatively, force tag to be set with
29+
# image: frontend:${TAG?TAG not set}
30+
build:
31+
context: ./frontend
32+
backend:
33+
image: backend:${TAG:-develop}
34+
build:
35+
context: ./backend
36+
```
37+
38+
We can instead do `TAG=my-branch docker compose build`, and docker compose will interpolate it for you. Still, not optimal. It involves typing _at least_ 4 extra characters, and if you're anything like me, you're going to forget more often than not. We _could_ export it globally, but global terminal variables tend to be scary.
39+
40+
Instead, we can use a [`justfile`](https://just.systems/man/en/) to perform a little bit of magic for us. A `justfile` is just a `makefile` without a million and a half footguns. I use them extensively and I love them. They also have a neat ability to set environment variables based on command execution.
41+
42+
```just
43+
export TAG=`(git rev-parse --abbrev-ref HEAD)`
44+
45+
just build *FLAGS:
46+
docker compose build {{FLAGS}}
47+
48+
just up *FLAGS:
49+
docker compose up {{FLAGS}}
50+
```
51+
52+
Pretty cool! Now, on `my-branch`, `docker compose build` will build our frontend and backend images and push them to our internal docker registry, correctly tagged with `my-branch`. This isn't as scary as it sounds in terms of space usage, as if your images are correctly layered, docker will de-duplicate the shared layers.
53+
54+
However, this could create docker tags that aren't syntactically valid, like if the branch is called `my/FEATURE-branch`. Instead, we can normalize it, following exactly what gitlab does to generate [`CI_COMMIT_REF_SLUG`](https://gitlab.com/gitlab-org/gitlab-runner/-/blame/af6932352f8ed15d1a6d9c786399607bc6be2c2d/Makefile.build.mk?page=1#L25).
55+
56+
```just
57+
export TAG=`(git rev-parse --abbrev-ref HEAD | tr '[:upper:]' '[:lower:] | cut -c -63 | sed -E 's/[^a-z0-9-]+/-/g' | sed -E 's/^-*([a-z0-9-]+[a-z0-9])-*$$/\1/g')`
58+
59+
just build *FLAGS:
60+
docker compose build {{FLAGS}}
61+
62+
just up *FLAGS:
63+
docker compose up {{FLAGS}}
64+
```
65+
66+
This branch would get normalized to `my-feature-branch`, all lowercase.
67+
# Appendix
68+
69+
## Parity with CI
70+
71+
Let's say you have a CI/CD process that builds containers and pushes them to your Gitlab/Github container registry. Assuming your CI tags and your docker compose tags are identical, you can pull your images directly from CI, bypassing a potentially expensive build step.
72+
73+
```yaml
74+
name: my-project-${TAG:-develop}
75+
services:
76+
frontend:
77+
image: ${REGISTRY}/frontend:${TAG:-develop}
78+
build:
79+
context: ./frontend
80+
backend:
81+
image: ${REGISTRY}/backend:${TAG:-develop}
82+
build:
83+
context: ./backend
84+
```
85+
86+
Where `${REGISTRY}` is `gitlab.com:5050/my/project/registry` or whatever. Now, with a modified `justfile`:
87+
88+
```just
89+
export TAG=`(git rev-parse --abbrev-ref HEAD | tr '[:upper:]' '[:lower:] | cut -c -63 | sed -E 's/[^a-z0-9-]+/-/g' | sed -E 's/^-*([a-z0-9-]+[a-z0-9])-*$$/\1/g')`
90+
export REGISTRY=gitlab.com:5050/my/project/registry # note that I would probably set this in a .env file since it's static
91+
92+
just pull *FLAGS:
93+
docker compose up {{FLAGS}}
94+
95+
just up *FLAGS:
96+
docker compose up {{FLAGS}}
97+
```
98+
99+
`just pull` can pull the base images, potentially saving a huge amount of time on initial build if layering is done correctly.
100+
101+
For a bonus-bonus round, if you use buildkit caching ([github](https://docs.docker.com/build/ci/github-actions/cache/) and [gitlab](https://docs.gitlab.com/ee/ci/docker/docker_layer_caching.html)), you can use the `cache_from` directive to save yourself some substantial time by pre-seeding your cache with dependencies in python, typescript, etc (again, assuming you are cache-mounting your layers correctly).
102+
103+
```yaml
104+
name: my-project-${TAG:-develop}
105+
services:
106+
frontend:
107+
image: ${REGISTRY}/frontend:${TAG:-develop}$
108+
build:
109+
context: ./frontend
110+
cache_from:
111+
- ${REGISTRY}/frontend:buildcache
112+
backend:
113+
image: ${REGISTRY}/backend:${TAG:-develop}$
114+
build:
115+
context: ./backend
116+
cache_from:
117+
- ${REGISTRY}/frontend:buildcache
118+
```
119+
## Using the tag to achieve pure isolation
120+
121+
If you're running a multiple copies on your machine, say, to test multiple branches, you can use the `name` top-level element to achieve pure isolation between each stack.
122+
123+
```yaml
124+
name: my-project-${TAG:-develop}
125+
services:
126+
frontend:
127+
image: frontend:${TAG:-develop}$
128+
build:
129+
context: ./frontend
130+
backend:
131+
image: backend:${TAG:-develop}$
132+
build:
133+
context: ./backend
134+
```
135+
136+
Let's say you're doing feature development on tag `my-feature`, but you need to switch to a new branch `my-hotfix` . You can `git switch`, and then with `just up`, it creates a set of containers entirely prefixed with `my-project-${TAG}` without conflicting with the original set of containers. If your spin up process is expensive, this can be a huge time-saver.
137+
138+
Additionally, if you have a local docker volume, say to persist database data, the volume is created with `my-project-${TAG}` as its prefix. Your data won't be polluted between branches, so you can perform database migrations, seeding, etc without getting into a funky state.

content/programming/tools/justfile/tagged-docker-images.md

Lines changed: 0 additions & 56 deletions
This file was deleted.

0 commit comments

Comments
 (0)