Skip to content

Instantly share code, notes, and snippets.

@Luzifer
Last active November 18, 2023 17:22
Show Gist options
  • Save Luzifer/7c54c8b0b61da450d10258f0abd3c917 to your computer and use it in GitHub Desktop.
Save Luzifer/7c54c8b0b61da450d10258f0abd3c917 to your computer and use it in GitHub Desktop.
Running docker-compose as a systemd service

Running docker-compose as a systemd service

Files

File Purpose
/etc/compose/docker-compose.yml Compose file describing what to deploy
/etc/systemd/system/docker-compose-reload.service Executing unit to trigger reload on docker-compose.service
/etc/systemd/system/docker-compose-reload.timer Timer unit to plan the reloads
/etc/systemd/system/docker-compose.service Service unit to start and manage docker compose

Installation

Put the above mentioned files in the corresponding places and let systemd load them:

# systemctl daemon-reload
# systemctl enable --now docker-compose.service docker-compose-reload.timer

Ansible role

The method shown here is also available as an Ansible role here: luzifer-ansible/docker-compose

[Unit]
Description=Refresh images and update containers
[Service]
Type=oneshot
ExecStart=/bin/systemctl reload-or-restart docker-compose.service
[Unit]
Description=Refresh images and update containers
Requires=docker-compose.service
After=docker-compose.service
[Timer]
OnCalendar=*:0/15
[Install]
WantedBy=timers.target
[Unit]
Description=Docker Compose container starter
After=docker.service network-online.target
Requires=docker.service network-online.target
[Service]
WorkingDirectory=/etc/compose
Type=oneshot
RemainAfterExit=yes
ExecStartPre=-/usr/local/bin/docker-compose pull --quiet
ExecStart=/usr/local/bin/docker-compose up -d
ExecStop=/usr/local/bin/docker-compose down
ExecReload=/usr/local/bin/docker-compose pull --quiet
ExecReload=/usr/local/bin/docker-compose up -d
[Install]
WantedBy=multi-user.target
@superrandres
Copy link

Hello friend, I have this error but the service is active and running perfectly. Please help me!

● docker-compose-reload.service - Refresh images and update containers
   Loaded: loaded (/etc/systemd/system/docker-compose-reload.service; static; vendor preset: enabled)
   Active: inactive (dead)

Mar 27 23:01:32 educa-inicial systemd[1]: docker-compose-reload.service: Unit entered failed state.
Mar 27 23:01:32 educa-inicial systemd[1]: docker-compose-reload.service: Failed with result 'exit-code'.
Mar 27 23:04:12 educa-inicial systemd[1]: Starting Refresh images and update containers...
Mar 27 23:04:12 educa-inicial systemctl[26601]: docker-compose.service is not active, cannot reload.
Mar 27 23:04:12 educa-inicial systemd[1]: docker-compose-reload.service: Main process exited, code=exited
Mar 27 23:04:12 educa-inicial systemd[1]: Failed to start Refresh images and update containers.
Mar 27 23:04:12 educa-inicial systemd[1]: docker-compose-reload.service: Unit entered failed state.
Mar 27 23:04:12 educa-inicial systemd[1]: docker-compose-reload.service: Failed with result 'exit-code'.
Mar 27 23:07:54 educa-inicial systemd[1]: Starting Refresh images and update containers...
Mar 27 23:07:58 educa-inicial systemd[1]: Started Refresh images and update containers.

@ShoGinn
Copy link

ShoGinn commented Apr 5, 2018

Awesome use of the systems timers.. thanks for the idea.

@irelandjoe
Copy link

And if I need to have delays between containers? e.g. trying to start up EdgeX foundry and they have recommendations for the delay to start up between services?

@Luzifer
Copy link
Author

Luzifer commented Apr 6, 2018

And if I need to have delays between containers?

You can use all features from Docker Compose (at the time of writing this format version 3)… It's just a systemd wrapper around docker-compose

@andycee
Copy link

andycee commented Apr 10, 2018

Thank you! You saved my time today.

@EugenMayer
Copy link

Thank you for sharing this. Eventhough i find your approach clean and fairly complete, i ask myself why most ( all ) people insist on using down on stop and not stop - @Luzifer could you elaborate?

I mean, i am not talking about those people how use down -v since they entirely do not understand the lifecycle, i still ask myself, why down in your case.

This will take a considirable amount of time longer to start up since all containers need to be recreated. I understand, this is dockerish - but throwing away containers just like that is not what is intended. I understand, that if i container is flapping / erroring, just recreate it, do not even bother - recreate instantly by all means.

But down + up takes time, this adds up to downtime and without having a particular reason for that just does not sound to me.


Are there technical reasons? i could think about a specific scenario, and that is the difference between "down + up" and up with a "newer image" which will recreate the container anyway. While the down - up will properly delete the anon volumes and thus the created container is really fresh, up with a newer image ( aka recreate ) will reuse all anon volumes - this is a pretty critical detail and could lead to issues with so called code containers and most volume_from designed mounts ( to e.g. forward proxies like nginx and so on ).

Did you consider that is specific as a tradeoff or what are your thoughts on this?

@Luzifer
Copy link
Author

Luzifer commented Apr 17, 2018

@EugenMayer no, there were no considerations about using up & down instead of up & stop… One thing I'm asking myself: The documentation of stop talks about the containers will be re-startable using start: will up start them also?

If so, in the most cases stop will definitely the better solution than down especially as this is intended to be a solution for long-running containers / container orchestration in opposite to a pure development approach.

The only reason for down would be to have a clean system set up on boot instead of reviving the old state but I'm currently not able to imagine why someone wants to do this…

Anyway, thanks for this comment! I'm going to modify my ansible-role mentioned above to be configurable which command to use and test the changes from there for my usecases… That way both factions are able to use their preferred solution.

@Ricordel
Copy link

Ricordel commented Aug 6, 2018

Note: there is one quirk with that pattern that has been a problem for me: if the ExecStartPre command fails, then the service is failed forever and will never start (Restart= is not supported on oneshot services, see systemd/systemd#2582).
I don't have a convincing alternative to offer though, maybe changing reload into reload-or-restart in the timer, at least it would be restarted periodically if it fails.

@Luzifer
Copy link
Author

Luzifer commented Aug 16, 2018

@Ricordel you're right, this leads to broken deployments in case for example the registry is not available (had exactly that case this morning). I've made two changes to this Gist and also to the Ansible role:

  • Used reload-or-restart as you suggested
  • Made the docker-compose pull call non-mandantory

That way the start will not fail over the pull (it might start slightly older images but hey, better not the recent image than no container, right?) and ensure the service is started…

@pomazanbohdan
Copy link

pomazanbohdan commented Mar 9, 2019

Please raise the contents of docker-compose.service above the other two

image

or

image

@GoSpursGoNL
Copy link

Thank you. The first time I ran the last command, to start the services, I got this error:
sudo systemctl start docker-compose docker-compose-reload
docker-compose.service is not active, cannot reload.
Job for docker-compose-reload.service failed because the control process exited with error code.
See "systemctl status docker-compose-reload.service" and "journalctl -xe" for details.

Then I found out that docker-compose perfectly started. Then again I ran just the reload-service
sudo systemctl start docker-compose-reload

And it runs perfectly.

@Luzifer
Copy link
Author

Luzifer commented Nov 3, 2019

@GoSpursGoNL I've updated the README above. This should work better as the previous version started the docker-compose-reload.service instead as intended the docker-compose-reload.timer.

@GoSpursGoNL
Copy link

Thanks, looks good. Will the timer also pull image updates (so update software to newer versions)? I prefer to do that manually since some applcations that I use can be unstable until they provide a bugfix release. Should I disable the timer?

@Luzifer
Copy link
Author

Luzifer commented Nov 3, 2019

If you don't want automated updates you can either pin the specific image (image: alpine:3.9), then only that specific tag will be used (that's the way I'm using for unstable software) or you can disable the timer which does not fully save you from updates as docker-compose itself has update-checks built in and even though local versions are preferred there might be updates.

The most safe way to guarantee nothing changes is to use sha-pinning:
image: sha256:965ea09ff2ebd2b9eeec88cd822ce156f6674c7e99be082c7efac3c62f3ff652

(Though the last method is possible I wouldn't use it as then even security updates for the tag are no longer possible.)

@leeramsay
Copy link

I think I found a typo, ExecStartPre=-/usr/local/bin/docker-compose pull --quiet
should be this ExecStartPre=/usr/local/bin/docker-compose pull --quiet
shouldn't it?

@Luzifer
Copy link
Author

Luzifer commented Jul 25, 2020

@leeramsay Nah that's intentional. The - in front of the command tells systemd not to panic if the command exits non-zero (see systemd.service(5))… If an image cannot be pulled because of some registry had trouble that's okay at that point. The image will be updated later if it's already present on the machine. If not the next step will then cause an error…

@leeramsay
Copy link

Okie doke, thanks for the info!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment