summaryrefslogtreecommitdiff
path: root/src/_posts/2021-06-04-managing-docker-compose-projects-with-openrc.md
blob: 76fe2725999346903d8d123b14356529f0d03fb7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
---
layout: post
tags: Gentoo Openrc Docker DockerCompose
title: Managing Docker Compose with OpenRC
social:
  email: mailto:~tyil/public-inbox@lists.sr.ht&subject=managing docker compose with openrc
description: >
  A quick overview of the method I've used to manage docker-compose based
  projects/services with OpenRC.
---

On one of my machines, I host a couple services using `docker-compose`. I
wanted to start/restart/stop these using the default init/service manager used
on the machine, `openrc`. This would allow them to start/stop automatically
with Docker (which coincides with the machine powering on or off,
respectively).

I've set this up through a single `docker-compose` meta-service. To add new
`docker-compose` projects to be managed, all I need to do for `openrc`
configuration is creating a symlink, and configure the path to the
`docker-compose.yaml` file.

The meta-service lives at `/etc/init.d/docker-compose`, just like all other
services managed by `openrc`. This file is quite straightforward. To start off,
a number of variables are set and exported.

{% highlight sh %}
name="$RC_SVCNAME"
description="OpenRC script for managing the $name docker-compose project"

# Set default values
DOCKER_COMPOSE="${DOCKER_COMPOSE:-docker-compose} $DOCKER_COMPOSE_ARGS"

COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-$name}"

# Export all variables used by docker-compose CLI
export COMPOSE_PROJECT_NAME
export COMPOSE_FILE
export COMPOSE_PROFILES
export COMPOSE_API_VERSION
export DOCKER_HOST
export DOCKER_TLS_VERIFY
export DOCKER_CERT_PATH
export COMPOSE_HTTP_TIMEOUT
export COMPOSE_TLS_VERSION
export COMPOSE_CONVERT_WINDOWS_PATHS
export COMPOSE_PATH_SEPARATOR
export COMPOSE_FORCE_WINDOWS_HOST
export COMPOSE_IGNORE_ORPHANS
export COMPOSE_PARALLEL_LIMIT
export COMPOSE_INTERACTIVE_NO_CLI
export COMPOSE_DOCKER_CLI_BUILD
{% endhighlight %}

One of the services I use is also configured with its own `external` network. I
want it to be created if it doesn't exist, to ensure that the service can start
up properly. I do *not* want it to be removed, so I left that out.

{% highlight sh %}
# Set up (external) networks
for name in "${DOCKER_NETWORKS[@]}"
do
        # Create the network if needed
        if ! docker network ls | awk '{ print $2 }' | grep -q "$name"
        then
                einfo "Creating docker network '$name'"
                docker network create "$name" > /dev/null
        fi

        # Expose some variables for the networks
        network_id="DOCKER_NETWORK_${name}_ID"

        declare -gx DOCKER_NETWORK_${name}_ID="$(docker network ls | awk '$2 == "'"$name"'" { print $1 }')"
        declare -gx DOCKER_NETWORK_${name}_GATEWAY="$(docker network inspect "${!network_id}" | jq -r '.[0].IPAM.Config[0].Gateway')"

        unset network_id
done
{% endhighlight %}

And lastly, there's the four simple functions to declare dependencies,
configure how to `start` or `stop`, and how to get the `status` of the service.

{% highlight sh %}
depend() {
        need docker
}

start() {
        $DOCKER_COMPOSE --project-directory "$COMPOSE_PROJECT_DIRECTORY" up -d
}

status() {
        $DOCKER_COMPOSE --project-directory "$COMPOSE_PROJECT_DIRECTORY" ps
}

stop() {
        $DOCKER_COMPOSE --project-directory "$COMPOSE_PROJECT_DIRECTORY" down
}
{% endhighlight %}

Now, to actually create a service file to manage a `docker-compose` project, a
symlink must be made. I'll take my
[`botamusique`](https://github.com/azlux/botamusique) service as an example.

{% highlight text %}
ln -s /etc/init.d/docker-compose /etc/init.d/botamusique
{% endhighlight %}

This service can't start just yet, as there's no `$COMPOSE_PROJECT_DIRECTORY`
configured for it yet. For this, a similarly named file should be made in
`/etc/conf.d`. In here, any variable used by the service can be configured.

{% highlight text %}
$EDITOR /etc/conf.d/botamusique
{% endhighlight %}

In my case, it only pertains the `$COMPOSE_PROJECT_DIRECTORY` variable.

{% highlight sh %}
COMPOSE_PROJECT_DIRECTORY="/var/docker-compose/botamusique"
{% endhighlight %}

And that's it. For additional `docker-compose` projects I need to make only a
symlink and a configuration file. If I discover a bug or nuisance, only a
single file needs to be altered to get the benefit on all the `docker-compose`
services.

For reference, here's the full `/etc/init.d/docker-compose` file.

{% highlight sh %}
#!/sbin/openrc-run
# Copyright 2021 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2

name="$RC_SVCNAME"
description="OpenRC script for managing the $name docker-compose project"

# Set default values
DOCKER_COMPOSE="${DOCKER_COMPOSE:-docker-compose} $DOCKER_COMPOSE_ARGS"

COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-$name}"

# Export all variables used by docker-compose CLI
export COMPOSE_PROJECT_NAME
export COMPOSE_FILE
export COMPOSE_PROFILES
export COMPOSE_API_VERSION
export DOCKER_HOST
export DOCKER_TLS_VERIFY
export DOCKER_CERT_PATH
export COMPOSE_HTTP_TIMEOUT
export COMPOSE_TLS_VERSION
export COMPOSE_CONVERT_WINDOWS_PATHS
export COMPOSE_PATH_SEPARATOR
export COMPOSE_FORCE_WINDOWS_HOST
export COMPOSE_IGNORE_ORPHANS
export COMPOSE_PARALLEL_LIMIT
export COMPOSE_INTERACTIVE_NO_CLI
export COMPOSE_DOCKER_CLI_BUILD

# Set up (external) networks
for name in "${DOCKER_NETWORKS[@]}"
do
        # Create the network if needed
        if ! docker network ls | awk '{ print $2 }' | grep -q "$name"
        then
                einfo "Creating docker network '$name'"
                docker network create "$name" > /dev/null
        fi

        # Expose some variables for the networks
        network_id="DOCKER_NETWORK_${name}_ID"

        declare -gx DOCKER_NETWORK_${name}_ID="$(docker network ls | awk '$2 == "'"$name"'" { print $1 }')"
        declare -gx DOCKER_NETWORK_${name}_GATEWAY="$(docker network inspect "${!network_id}" | jq -r '.[0].IPAM.Config[0].Gateway')"

        unset network_id
done

depend() {
        need docker
}

start() {
        $DOCKER_COMPOSE --project-directory "$COMPOSE_PROJECT_DIRECTORY" up -d
}

status() {
        $DOCKER_COMPOSE --project-directory "$COMPOSE_PROJECT_DIRECTORY" ps
}

stop() {
        $DOCKER_COMPOSE --project-directory "$COMPOSE_PROJECT_DIRECTORY" down
}
{% endhighlight %}