Skip to content

Commit af6fb76

Browse files
committed
add systemd docker compose post
1 parent 8ba26dd commit af6fb76

File tree

3 files changed

+110
-1
lines changed

3 files changed

+110
-1
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
node_modules
22
*.lock
3-
resources/_gen
3+
resources/_gen
4+
public
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
---
2+
author: "Sam"
3+
title: "In-place rebuilding of a docker compose project with systemd"
4+
description: "Did you just restart prod again?"
5+
date: 2024-05-15
6+
tags: ["containerisation", "docker", "systemd"]
7+
thumbnail: /docker-systemd.jpg
8+
---
9+
10+
## The usual approach
11+
If you do a search about how to run docker compose with systemd, you get something similar to this:
12+
13+
```systemd
14+
[Unit]
15+
Description=Some docker compose service
16+
Requires=docker.service
17+
After=docker.service
18+
19+
[Service]
20+
Type=oneshot
21+
RemainAfterExit=true
22+
WorkingDirectory=/path/to/project
23+
ExecStart=/usr/bin/docker-compose up --build -d
24+
ExecStop=/usr/bin/docker-compose down
25+
26+
[Install]
27+
WantedBy=multi-user.target
28+
```
29+
30+
While this works, it has some issues:
31+
- No logs are output to the journal (all the logs for every container are on the docker daemon's service, however)
32+
- Restarting the service will take all the containers down before bringing them up again
33+
34+
There's also no automated restarts if a container fails, but you should manage that with a [restart policy on each service in the compose file](https://docs.docker.com/compose/compose-file/05-services/#restart).
35+
36+
## A (slightly) better approach
37+
One solution is to turn it into a regular unit so that we can use `docker-compose up` to attach to the confiners and print the logs:
38+
39+
```systemd
40+
[Unit]
41+
Description=Some docker compose service
42+
Requires=docker.service
43+
After=docker.service
44+
45+
[Service]
46+
WorkingDirectory=/path/to/project
47+
# wait for the containers to come up before the service is considered "started"
48+
ExecStartPre=/usr/bin/docker-compose up --build --wait
49+
# attach to the containers and print logs
50+
ExecStart=/usr/bin/docker-compose up
51+
# prevent docker-compose from bringing down the containers when restarting
52+
RestartKillSignal=SIGKILL
53+
54+
[Install]
55+
WantedBy=multi-user.target
56+
```
57+
58+
Note the exclusion of an `ExecStop`, instead we are relying on the SIGTERM sent when stopping the unit (this will gracefully stop the containers as expected).
59+
This also lets us use SIGKILL when restarting the unit, which force stops our `docker-compose up` - preventing it from stopping the containers when the service is restarted.
60+
61+
Unfortunately, stopping this way causes docker-compose to terminate with exit code 130, which systemd considers a failure:
62+
63+
```txt
64+
% systemctl stop something
65+
% systemctl status something
66+
× something.service - Some docker compose service
67+
Loaded: loaded (/usr/lib/systemd/system/something.service; enabled; preset: enabled)
68+
Active: failed (Result: exit-code) since Wed 2024-05-15 19:42:33 CAT; 54s ago
69+
Duration: 52.405s
70+
Process: 77957 ExecStart=/usr/bin/docker-compose up (code=exited, status=130)
71+
Main PID: 77957 (code=exited, status=130)
72+
CPU: 1.425s
73+
```
74+
75+
This could be silenced by putting a `-` in front of the command so it reads `ExecStart=-/usr/bin/docker-compose up`.
76+
77+
Alternatively, you could use a bash script to ignore only the 130 exit code:
78+
```bash
79+
#!/usr/bin/env bash
80+
/usr/bin/docker-compose up
81+
exit $(( $? == 130 ? 0 : $? ))
82+
```
83+
84+
And run it from the systemd unit like `ExecStart=/path/to/script.sh`
85+
86+
## A better approach
87+
The exit code shenanigans can be avoided if we used `docker-compose logs` for our main process instead:
88+
89+
```systemd
90+
[Unit]
91+
Description=Some docker compose service
92+
Requires=docker.service
93+
After=docker.service
94+
95+
[Service]
96+
WorkingDirectory=/path/to/project
97+
ExecStartPre=/usr/bin/docker-compose up --build --wait
98+
ExecStart=/usr/bin/docker-compose logs --follow -n 0
99+
ExecStop=/usr/bin/docker-compose down
100+
ExecReload=/usr/bin/docker-compose up --build --wait
101+
102+
[Install]
103+
WantedBy=multi-user.target
104+
```
105+
106+
This has the upside of keeping the expected behaviour of `systemctl restart <service>` ("stop and then start again").
107+
108+
The service can be reloaded using `systemctl reload <service>`. Which will run `ExecReload` without stopping the service beforehand.

static/docker-systemd.jpg

516 KB
Loading

0 commit comments

Comments
 (0)