<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Martin Goyot</title>
    <description>The latest articles on Forem by Martin Goyot (@erwyn).</description>
    <link>https://forem.com/erwyn</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F562526%2Ffc71175b-ec11-4d2f-9e04-38172ab12daa.jpeg</url>
      <title>Forem: Martin Goyot</title>
      <link>https://forem.com/erwyn</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/erwyn"/>
    <language>en</language>
    <item>
      <title>Compute Caddy payload using Jinja2 with Ansible</title>
      <dc:creator>Martin Goyot</dc:creator>
      <pubDate>Tue, 21 May 2024 18:32:21 +0000</pubDate>
      <link>https://forem.com/erwyn/compute-caddy-payload-using-jinja2-with-ansible-9el</link>
      <guid>https://forem.com/erwyn/compute-caddy-payload-using-jinja2-with-ansible-9el</guid>
      <description>&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;As I may have mentioned in previous articles I run my services using Ansible to deal with the deployment and upgrade of different components. If we generalize the setup, it mostly consists of a reverse proxy (Caddy) listening on 443 and app stacks running under docker on their own network that connects to Caddy's one. Something that looks like that:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uv7aZ-4s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://erwyn.piwany.com/resources/img/Server%2520app%2520stack%2520diagram.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uv7aZ-4s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://erwyn.piwany.com/resources/img/Server%2520app%2520stack%2520diagram.png" alt="Server stack diagram" width="800" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Issue
&lt;/h2&gt;

&lt;p&gt;My Ansible playbook is structured in a way where the reverse proxy is spawned first and then each app is deployed one by one, registering its own configuration against Caddy's API in order to be served. This way of structuring it has lead me to a nice place where each service is described as a var of my role which then iterates over each service. The var describes among other things the docker-compose definition, networking, volumes to be backed up, DNS, and the part that gets our attention today: the Caddy API payload. While I love this organization, I have recently grown weary of the time it takes to execute the complete playbook and as such I started looking for culprits. You guessed it, a big bottleneck is indeed the declaration to Caddy's API.&lt;/p&gt;

&lt;p&gt;For reference, here is the kind of vars describing the services we are talking about:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;dns_domain&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mydomain.com&lt;/span&gt;
&lt;span class="na"&gt;applications&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;dns_entry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;record&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app_a&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;xx.xx.xx.xx&lt;/span&gt;
      &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;present&lt;/span&gt;
    &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app_a.mydomain.com"&lt;/span&gt;
      &lt;span class="na"&gt;handle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;reverse_proxy&lt;/span&gt;
          &lt;span class="na"&gt;upstreams&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;dial&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app_a:8080"&lt;/span&gt;
      &lt;span class="na"&gt;terminal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;dns_entry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;record&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app_b&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;xx.xx.xx.xx&lt;/span&gt;
      &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;present&lt;/span&gt;
    &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app_b.mydomain.com"&lt;/span&gt;
      &lt;span class="na"&gt;handle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;reverse_proxy&lt;/span&gt;
          &lt;span class="na"&gt;upstreams&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;dial&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app_b:80"&lt;/span&gt;
      &lt;span class="na"&gt;terminal&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;vpn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The issue has nothing to do with Caddy itself which is a very nice piece of software, but everything to do with the architecture of the Playbook which falls on me. By pursuing this idea of declaring each service independently I found myself forced to do two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Having an &lt;a href="https://caddyserver.com/docs/api#using-id-in-json"&gt;ID&lt;/a&gt; for each part of the configuration so that I can query it back later when it eventually changes.&lt;/li&gt;
&lt;li&gt;Querying the API multiple times for each service.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The need of an ID is no big deal in itself, but I kinda dislike them when they are not really needed, you never know how it is going to evolve and if you're going to end up with conflicts or whatnot. But I'm forced to query the API multiple times during declaration which leads to a lot of unnecessary Ansible's tasks and HTTP calls. In fact, I need to call the API exactly 4 times in the worst case, worst case being that the service already has a configuration registered by Caddy:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Insert the hostname in the TLS automation part of Caddy's configuration.&lt;/li&gt;
&lt;li&gt;Get current configuration for service if any.&lt;/li&gt;
&lt;li&gt;Delete current configuration if it is different from the new one.&lt;/li&gt;
&lt;li&gt;Register the new configuration for the service.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For reference, here is an example of what Caddy's payload looks like in full:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"apps"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"servers"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"myserver"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="nl"&gt;"listen"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="s2"&gt;":443"&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="nl"&gt;"routes"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="nl"&gt;"handle"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                           &lt;/span&gt;&lt;span class="nl"&gt;"handler"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"reverse_proxy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                           &lt;/span&gt;&lt;span class="nl"&gt;"upstreams"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                                 &lt;/span&gt;&lt;span class="nl"&gt;"dial"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"app_a:8080"&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                           &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="nl"&gt;"match"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                           &lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="s2"&gt;"app_a.mydomain.com"&lt;/span&gt;&lt;span class="w"&gt;
                           &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="nl"&gt;"terminal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"myserver-vpn"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="nl"&gt;"listen"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="s2"&gt;":5556"&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="nl"&gt;"routes"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="nl"&gt;"handle"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                           &lt;/span&gt;&lt;span class="nl"&gt;"handler"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"reverse_proxy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                           &lt;/span&gt;&lt;span class="nl"&gt;"upstreams"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                                 &lt;/span&gt;&lt;span class="nl"&gt;"dial"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"app_b:80"&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                           &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="nl"&gt;"match"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                           &lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="s2"&gt;"app_b.mydomain.com"&lt;/span&gt;&lt;span class="w"&gt;
                           &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="nl"&gt;"terminal"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"tls"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"automation"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"policies"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nl"&gt;"issuers"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"challenges"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
                           &lt;/span&gt;&lt;span class="nl"&gt;"dns"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="nl"&gt;"provider"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
                                 &lt;/span&gt;&lt;span class="nl"&gt;"api_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"myapitoken"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                                 &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"myprovider"&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                           &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"acme"&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"challenges"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
                           &lt;/span&gt;&lt;span class="nl"&gt;"dns"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="nl"&gt;"provider"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="w"&gt;
                                 &lt;/span&gt;&lt;span class="nl"&gt;"api_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"myapitoken"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                                 &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"myprovider"&lt;/span&gt;&lt;span class="w"&gt;
                              &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                           &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"zerossl"&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="nl"&gt;"subjects"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="s2"&gt;"app_a.mydomain.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                     &lt;/span&gt;&lt;span class="s2"&gt;"app_b.mydomain.com"&lt;/span&gt;&lt;span class="w"&gt;
                  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
               &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here is what the Ansible playbook looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;## main.yml&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy application&lt;/span&gt;
  &lt;span class="na"&gt;include_tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy-application.yml&lt;/span&gt;
  &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;applications&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
  &lt;span class="na"&gt;loop_control&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;loop_var&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application&lt;/span&gt;

&lt;span class="c1"&gt;## deploy-application.yml&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;include_tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;register-dns.yml&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;include_tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;inject-reverse-proxy-payload.yml&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;include_tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy-compose-stack.yml&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;include_tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;connect-reverse-proxy-to-containers.yml&lt;/span&gt;

&lt;span class="c1"&gt;## inject-reverse-proxy-payload.yml&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Register hostname in TLS automation&lt;/span&gt;
  &lt;span class="c1"&gt;# Whatever needs to be done here&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get currently loaded configuration for route&lt;/span&gt;
  &lt;span class="c1"&gt;# Whatever needs to be done here&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Load configuration for route if needed&lt;/span&gt;
  &lt;span class="c1"&gt;# Whatever needs to be done here, DELETE and POST&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;After thinking it through, I realized that it does not make sense with our use case, we could potentially compute the whole payload at once and push it once. This would solve both of my issues: only one call to Caddy's API instead of 4xServices (worst case scenario), and no need to use IDs anymore as I won't be modifying parts of it anymore, if I need to update the configuration I re-send the whole one to Caddy.&lt;/p&gt;

&lt;p&gt;In order to implement this, we can leverage on Jinja2 which is the templating engine bundled with Ansible. The payload being "rather big", I prefer isolating it inside a template file instead of directly building it inside the playbook. I can then render the template inside a variable using &lt;code&gt;lookup('template', 'path/to/template.j2')&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The templates looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jinja"&gt;&lt;code&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;public_payloads_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;private_payloads_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;application&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;applications&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
    &lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s1"&gt;'vpn'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;application&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nv"&gt;application.vpn&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
        &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;private_payloads_list.append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;application.payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
    &lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
        &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;public_payloads_list.append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;application.payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
    &lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endif&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endfor&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="nv"&gt;domain_names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;application&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;applications&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
    &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;domain_names.append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;application.dns_entry.record&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;"."&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;dns_domain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endfor&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

{
    "apps": {
        "http": {
            "servers": {
                "&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;inventory_hostname&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;": {
                    "listen": [
                        ":443"
                    ],
                    "routes": &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;public_payloads_list&lt;/span&gt; &lt;span class="o"&gt;| &lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
                },
                "&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;inventory_hostname&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;-vpn": {
                    "listen": [
                        ":5556"
                    ],
                    "routes": &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;private_payloads_list&lt;/span&gt; &lt;span class="o"&gt;| &lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
                }
            }
        },
        "tls": {
            "automation": {
                "policies": [
                    {
                        "subjects": &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;domain_names&lt;/span&gt; &lt;span class="o"&gt;| &lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;,
                        "issuers": [
                            {
                                "challenges": {
                                    "dns": {
                                        "provider": {
                                            "api_token": "&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;provider_dns_secret&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;",
                                            "name": "myprovider"
                                        }
                                    }
                                },
                                "module": "acme"
                            },
                            {
                                "challenges": {
                                    "dns": {
                                        "provider": {
                                            "api_token": "&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;provider_dns_secret&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;",
                                            "name": "myprovider"
                                        }
                                   }
                                },
                                "module": "zerossl"
                            }
                        ]
                    }
                ]
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The template starts with two loops, one to render to json and dispatch the service's payload between &lt;code&gt;private_payloads_list&lt;/code&gt; and &lt;code&gt;public_payloads_list&lt;/code&gt; based on whether or not it is supposed to be access via the VPN, and the other one to collect and rebuild all the hostnames of the services. All thoses lists are then rendered inside the template after going through the &lt;code&gt;to_json&lt;/code&gt; filter in order to format them correctly for our payload.&lt;/p&gt;

&lt;p&gt;Now that the template is built, we can render it directly inside a variable but not before sending it through a &lt;code&gt;from_json&lt;/code&gt; filter so that it fails if anything is broken at this point. Overall, the Ansible playbook now looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;## main.yml&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;import_tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;register-dns.yml&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;import_tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;inject-reverse-proxy-payload.yml&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy application&lt;/span&gt;
  &lt;span class="na"&gt;include_tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy-application.yml&lt;/span&gt;
  &lt;span class="na"&gt;loop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;applications&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
  &lt;span class="na"&gt;loop_control&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;loop_var&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application&lt;/span&gt;

&lt;span class="c1"&gt;## inject-reverse-proxy-payload.yml&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Inject Caddy payload&lt;/span&gt;
  &lt;span class="na"&gt;vars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;caddy_payload&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;lookup('template',&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'../path/to/caddy-payload.json.j2')&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;|&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;from_json&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
  &lt;span class="na"&gt;community.docker.docker_container_exec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;caddy&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;curl -X POST http://localhost:2019/load&lt;/span&gt;
      &lt;span class="s"&gt;-H "Content-Type: application/json"&lt;/span&gt;
      &lt;span class="s"&gt;-d '{{ caddy_payload | to_json }}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The deployment of my stack is now much faster than it was. Of course there are other areas of my playbook that could still be optimized but this removed the most obvious time consuming part of it. I guess the conlusion is that although I philosophically preferred the previous way of dealing with services registration in Caddy in the sense that it felt more "encapsulated", this new version is much more practical and elegant (no more IDs yay), much so that I tried to apply the same principles to the dns registration part but sadly could not find a way as it seems that the Ansible module for my provider only allows the definition of one domain at a time.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>ansible</category>
      <category>caddy</category>
    </item>
    <item>
      <title>Could we stop prefixing booleans with the verb ?</title>
      <dc:creator>Martin Goyot</dc:creator>
      <pubDate>Wed, 07 Feb 2024 16:13:28 +0000</pubDate>
      <link>https://forem.com/erwyn/could-we-stop-prefixing-booleans-with-the-verb--na9</link>
      <guid>https://forem.com/erwyn/could-we-stop-prefixing-booleans-with-the-verb--na9</guid>
      <description>&lt;p&gt;A widespread convention when it comes to Boolean variables naming is to prefix them with a verb. Sometimes the explanation given for this practice is that it helps reading code, other times that this way we directly can spot that this is indeed a boolean. I would like to discuss those two assumptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  It helps reading code
&lt;/h2&gt;

&lt;p&gt;Maybe this is just me but I like when code reads like instructions. I agree that it can't always be the case but I feel I should strive for it whenever I can. Keeping that in mind we can look at this complex piece of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;isUserLoggedIn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we use the conventional prefixing, and the voice in my head reads: "If is user logged in...".&lt;/p&gt;

&lt;p&gt;Now, if we consider this piece of code without prefixing I read "If user is logged in...":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nx"&gt;userIsLoggedIn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See, nothing spectacular, this won't change your life but still I feel like the second version is much more flowing than the first one, while prefixing "hurts" the readability. Please do note that we keep the &lt;code&gt;is&lt;/code&gt; or &lt;code&gt;has&lt;/code&gt; verbs, but at their natural placement, not as prefix.&lt;/p&gt;

&lt;h2&gt;
  
  
  It helps spotting that it is a Boolean
&lt;/h2&gt;

&lt;p&gt;Let me be honest: this is totally true. Whenever I encounter a variable prefixed with this &lt;code&gt;is&lt;/code&gt; or &lt;code&gt;has&lt;/code&gt;, I definitely know that this is a Boolean.&lt;/p&gt;

&lt;p&gt;What I would like to challenge here, is the &lt;strong&gt;need&lt;/strong&gt; behind this, the use case. Do I really need to know that this is a boolean at this specific point in time. This might be terribly personal, but I distinguish at least two ways of reading code. The first one, which refers to the previous part of this short post, when I want to understand the flow of the code, and the second one, when I'm diving into the code to edit it or spot an bug.&lt;/p&gt;

&lt;p&gt;When I'm reading the code in a "I want to understand the flow" way, I don't really need to know that this is a boolean, I need a well named variable that helps me read what is happening, the fact that this is a boolean is a mere implementation detail that I don't need to know about. Moreover, I feel that reading &lt;code&gt;userIsLoggedIn&lt;/code&gt; leave very few room to interpret it as something else than a boolean. On the contrary, if I'm diving into the code to edit it or spot a bug, in this case, yes, I need to know. But, if I'm in this mindset, I'm much more into "details mode" and I will definitely check the type of my variable which will be declared in there and this will be the only source of truth for me, not the fact that it starts with &lt;code&gt;is&lt;/code&gt; or &lt;code&gt;has&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I guess that all this gibberish is about is that I try to prioritize readability when it comes to writing code, and I feel like this convention can hurt it. Now, if you are in a team where prefixing is a widespread convention, please do follow this rule or challenge it with the team but don't go rogue; being a team player is the one of the most important thing.&lt;/p&gt;

</description>
      <category>development</category>
      <category>cleancoding</category>
    </item>
    <item>
      <title>How to backup your selfhosted server running under docker-compose</title>
      <dc:creator>Martin Goyot</dc:creator>
      <pubDate>Wed, 17 Feb 2021 21:38:18 +0000</pubDate>
      <link>https://forem.com/erwyn/how-to-backup-your-selfhosted-server-running-under-docker-compose-2m68</link>
      <guid>https://forem.com/erwyn/how-to-backup-your-selfhosted-server-running-under-docker-compose-2m68</guid>
      <description>&lt;p&gt;When you go down the rabbithole of trying to selfhost your internet life there are two concerns that will arise quite rapidly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to be able to respawn everything quickly from scratch if it fails&lt;/li&gt;
&lt;li&gt;How to make sure I won't loose my data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The first part is mainly covered by your use of Docker Compose, but the second one can be a bit tedious to think through.&lt;/p&gt;

&lt;p&gt;In this article I will propose a backup scenario. Beware, this is in no way a guaranteed approach to backup but rather an idea of one could do this. I in no way endorse any issue that would arise on your side while implementing this scenario.&lt;/p&gt;

&lt;h2&gt;
  
  
  Description of the Backup Scenario
&lt;/h2&gt;

&lt;p&gt;In this scenario we will try to cover ourselves by following the 321 Rule:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;3 copies at least&lt;/li&gt;
&lt;li&gt;2 locations&lt;/li&gt;
&lt;li&gt;1 off-site&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In order to follow this rule we have basically two needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A backup tool that we can trust&lt;/li&gt;
&lt;li&gt;A way to synchronize the backup repository to another site&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the backup part we will be using Restic, a nice backup tool that encrypts the backups.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://restic.net/"&gt;restic · Backups done right!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As to synchronizing the backup repository with a off-site location, we will use RClone. Rclone is like RSync but designed to discuss with Cloud backends like BackBlaze, Mega, S3 etc...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://rclone.org/"&gt;Rclone - Rclone syncs your files to cloud storage: Google Drive, S3, Swift, Dropbox, Google Cloud Storage, Azure, Box and many more.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Initialization of the Restic repository
&lt;/h2&gt;

&lt;p&gt;The first thing to do is to initialize our Restic repository. As per usual we are going to use Docker images to use both tools. To do so we are going to bind mount a place of the host file system were we will store the repository. So go where you want to store it and then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-ti&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;:/data restic/restic init &lt;span class="nt"&gt;--repo&lt;/span&gt; /data/my-restic-repo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will be asked for a password for the encryption of the repository, do not loose it, and then you are left with a new folder on your file system: my-restic-repo&lt;/p&gt;

&lt;h2&gt;
  
  
  Initialization of the RClone configuration
&lt;/h2&gt;

&lt;p&gt;Same as the initialization of the Restic repository, we are going to use a Docker image for this. We will also mount the file system for this. The command to use is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-ti&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;:/data rclone/rclone &lt;span class="nt"&gt;--config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/data/rclone-config config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I will not tell you what to configure for RClone as it highly depends on where you will want to store it, which cloud backend, and so on. You will find the documentation &lt;a href="https://rclone.org/docs/"&gt;at this address&lt;/a&gt;. This said, as a hint, in this article we will use the &lt;code&gt;crypt&lt;/code&gt; backend in combination with another cloud backend. So basically it is the crypted remote that will be targeted which himself targets the remote cloud backend I want to use like &lt;code&gt;crypt -&amp;gt; S3&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure volumes and exclusions
&lt;/h2&gt;

&lt;p&gt;Now that our tools are configured, we want to set a list of docker volumes that will be backed up. In order to do that, we will simply create a file &lt;code&gt;volumes&lt;/code&gt; and list volumes from our &lt;code&gt;docker-compose.yml&lt;/code&gt; file that we want to be backed up. We chose to do this because as of today it's not possible to reposition labels on already created docker volumes, but there is work in the way regarding that, and once this is released we will be able to tag the volumes directly in the &lt;code&gt;docker-compose.yml&lt;/code&gt; file and just list all docker volumes filtered with this tag, which could be more convenient.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;service1-config
service1-data
service2-config
service2-data
service3-config
service3-data

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we listed the volumes to be backed up, we will configure exclusions as there are probably files or folders in those volumes that we don't really want to store (tmp files, transfer files, whatever bulky unimportant files).&lt;/p&gt;

&lt;p&gt;In order to do this, we will create an &lt;code&gt;exclusions&lt;/code&gt; file and list all exclusions &lt;a href="https://restic.readthedocs.io/en/stable/040_backup.html#excluding-files"&gt;following the Restic exclusion format&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;service1-config/data/transcodes
service1-config/data/transcoding-temp
service2-data/data/SomeUser/files_trashbin

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Writing the passwords
&lt;/h2&gt;

&lt;p&gt;In order to provide the encryption passwords both for Restic and for the RClone configuration file, we will write them down in two different files. This is a very simple way to deal with the matter, one could use &lt;a href="https://docs.docker.com/engine/swarm/secrets/"&gt;Docker Secrets&lt;/a&gt;, Vault, or whatever to better store those passwords. For the example we will store them individually in a &lt;code&gt;restic-password&lt;/code&gt; and &lt;code&gt;rclone-password&lt;/code&gt; file.&lt;/p&gt;

&lt;h2&gt;
  
  
  The backup script
&lt;/h2&gt;

&lt;p&gt;Okay, we are good to take it to the next step. We have our volumes and exclusions set up, the passwords, the RClone config and the Restic repository. Now we need a script. Here is an example bash script that will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Stop all our containers&lt;/li&gt;
&lt;li&gt;Backup the listed volumes&lt;/li&gt;
&lt;li&gt;Prune the old backups following a given strategy (&lt;a href="https://restic.readthedocs.io/en/stable/060_forget.html#removing-snapshots-according-to-a-policy"&gt;see this Restic documentation&lt;/a&gt;). We are going to keep 7 daily 4 weekly 12 montly.&lt;/li&gt;
&lt;li&gt;Clone the Restic repository to an off-site location with RClone&lt;/li&gt;
&lt;li&gt;Start all the containers back
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="nv"&gt;COMPOSE_FILE_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;
&lt;span class="nv"&gt;COMPOSE_PROJECT_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;
&lt;span class="nv"&gt;RESTIC_REPOSITORY_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt;
&lt;span class="nv"&gt;VOLUME_LIST_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$4&lt;/span&gt;
&lt;span class="nv"&gt;RESTIC_EXCLUSION_FILE_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$5&lt;/span&gt;
&lt;span class="nv"&gt;RESTIC_PASSWORD_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$6&lt;/span&gt;
&lt;span class="nv"&gt;MACHINE_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$7&lt;/span&gt;
&lt;span class="nv"&gt;RCLONE_CONFIG_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$8&lt;/span&gt;
&lt;span class="nv"&gt;RCLONE_PASSWORD_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$9&lt;/span&gt;
&lt;span class="nv"&gt;RCLONE_REMOTE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;10&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;function &lt;/span&gt;stop_all_containers &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Stopping all running containers"&lt;/span&gt;
  &lt;span class="nv"&gt;COMPOSE_HTTP_TIMEOUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;200 docker-compose &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;$COMPOSE_FILE_PATH&lt;/span&gt; stop
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;function &lt;/span&gt;list_all_volumes &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;list&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;volumes&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="nv"&gt;$VOLUME_LIST_PATH&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;volume_name &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nv"&gt;list&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;list&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; -v &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;COMPOSE_PROJECT_NAME&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;volume_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:/source/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;volume_name&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;done
  &lt;/span&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"'&lt;/span&gt;&lt;span class="nv"&gt;$list&lt;/span&gt;&lt;span class="s2"&gt;'"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;function &lt;/span&gt;backup_volumes &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Retrieving volumes list"&lt;/span&gt;
  list_all_volumes volumes_list

  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Starting backup"&lt;/span&gt;
  docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
          &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;$RESTIC_REPOSITORY_PATH&lt;/span&gt;:/data/restic_repository &lt;span class="se"&gt;\&lt;/span&gt;
          &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;$RESTIC_EXCLUSION_FILE_PATH&lt;/span&gt;:/data/excludes &lt;span class="se"&gt;\&lt;/span&gt;
          &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;$RESTIC_PASSWORD_PATH&lt;/span&gt;:/data/password &lt;span class="se"&gt;\&lt;/span&gt;
          &lt;span class="nv"&gt;$volumes_list&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
          restic/restic &lt;span class="nt"&gt;-r&lt;/span&gt; /data/restic_repository backup /source &lt;span class="nt"&gt;--exclude-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/data/excludes &lt;span class="nt"&gt;--password-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/data/password &lt;span class="nt"&gt;--host&lt;/span&gt; &lt;span class="nv"&gt;$MACHINE_NAME&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;function &lt;/span&gt;start_all_containers &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Starting back all containers"&lt;/span&gt;
  &lt;span class="nv"&gt;COMPOSE_HTTP_TIMEOUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;200 docker-compose &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nv"&gt;$COMPOSE_FILE_PATH&lt;/span&gt; start
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;function &lt;/span&gt;prune_old_backups &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Pruning old backups if needed"&lt;/span&gt;
  docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
          &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;$RESTIC_REPOSITORY_PATH&lt;/span&gt;:/data/restic_repository &lt;span class="se"&gt;\&lt;/span&gt;
          &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;$RESTIC_PASSWORD_PATH&lt;/span&gt;:/data/password &lt;span class="se"&gt;\&lt;/span&gt;
          restic/restic &lt;span class="nt"&gt;-r&lt;/span&gt; /data/restic_repository forget &lt;span class="nt"&gt;--keep-daily&lt;/span&gt; 7 &lt;span class="nt"&gt;--keep-weekly&lt;/span&gt; 4 &lt;span class="nt"&gt;--keep-monthly&lt;/span&gt; 12 &lt;span class="nt"&gt;--password-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/data/password &lt;span class="nt"&gt;--host&lt;/span&gt; &lt;span class="nv"&gt;$MACHINE_NAME&lt;/span&gt; &lt;span class="nt"&gt;--prune&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;function &lt;/span&gt;clone_restic_repository &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Syncing the Restic repository on the cloud"&lt;/span&gt;
  docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
          &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;$RCLONE_CONFIG_PATH&lt;/span&gt;:/data/config &lt;span class="se"&gt;\&lt;/span&gt;
          &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="nv"&gt;$RESTIC_REPOSITORY_PATH&lt;/span&gt;:/data/repo &lt;span class="se"&gt;\&lt;/span&gt;
          &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;RCLONE_CONFIG_PASS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="nv"&gt;$RCLONE_PASSWORD_PATH&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
          rclone/rclone &lt;span class="nt"&gt;--config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/data/config &lt;span class="nt"&gt;--stats-log-level&lt;/span&gt; NOTICE &lt;span class="nt"&gt;--stats&lt;/span&gt; 45m &lt;span class="nb"&gt;sync&lt;/span&gt; /data/repo &lt;span class="nv"&gt;$RCLONE_REMOTE&lt;/span&gt;:
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Starting backup process"&lt;/span&gt;
stop_all_containers
backup_volumes
prune_old_backups
clone_restic_repository
start_all_containers
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Done"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, we have our entire backup process set up. Just try to run it to see where you could have misconfigured things, it should go through smoothly, if not make the needed adjustments. Once everything is working fine, congratulations, you are now able to backup your server and have the repository backed up off-site!&lt;/p&gt;

&lt;p&gt;We can now proceed to the last part!&lt;/p&gt;

&lt;h2&gt;
  
  
  Automating the backup process
&lt;/h2&gt;

&lt;p&gt;Right now, in order to launch a backup, we have to call our script. But what we really want is it to be run automatically by the server maybe once a day. For this, we are going to use systemd timers' capabilities. Long story short, we are going to create a systemd service that runs the backup script and a timer associated to it to run it daily.&lt;/p&gt;

&lt;p&gt;For that, all we need is a service file like that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;///etc/systemd/system/backup.service
[Unit]
Description=Run the backup of the server

[Service]
Type=simple
ExecStart=call/to/script/with/your/arguments
StandardError=journal
User=userThatRunsDockerCompose

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the associated timer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;///etc/systemd/backup.timer
[Unit]
Description=Daily run of backup script

[Timer]
OnCalendar=*-*-* 04:00:00
Persistent=true

[Install]
WantedBy=timers.target

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we are done! All we have to do is to enable and start the service and the timer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sudo systemctl enable backup.service&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sudo systemctl enable backup.timer&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sudo systemctl start backup.timer&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And you are now done ! Your backup script will run everyday at 4am! If you want further information on how to see the logs and so on, look into systemctl and journalctl you will find all you need, but quick hint: &lt;code&gt;journalctl -u backup.service --since today&lt;/code&gt; will give you everything that happened today for your backup.&lt;/p&gt;

&lt;p&gt;-- Amike&lt;/p&gt;

</description>
      <category>docker</category>
      <category>backup</category>
    </item>
    <item>
      <title>Add a Play module from your own Nexus Repository</title>
      <dc:creator>Martin Goyot</dc:creator>
      <pubDate>Mon, 18 Sep 2017 16:00:00 +0000</pubDate>
      <link>https://forem.com/erwyn/add-a-play-module-from-your-own-nexus-repository-3c07</link>
      <guid>https://forem.com/erwyn/add-a-play-module-from-your-own-nexus-repository-3c07</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HKLn_vOA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://erwyn.piwany.com/content/images/2017/09/typesafe_sbt_svg-4.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HKLn_vOA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://erwyn.piwany.com/content/images/2017/09/typesafe_sbt_svg-4.svg" alt="Add a Play module from your own Nexus Repository"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Those days I'm mainly working with the &lt;a href="https://www.playframework.com/"&gt;Play! Framework&lt;/a&gt; for Java. We are in a microservices setup and thus we end up having some common code between the different services. We decided to make Play Modules that we would import in the different projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the Module
&lt;/h2&gt;

&lt;p&gt;In order to do so, we created a project with a few utility classes that we published using SBT and our own Nexus respository. The final configuration ended up being something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name := "some-module"
organization := "my.organization"
version := "1.0.0-SNAPSHOT"
scalaVersion := "2.12.2"
lazy val root = (project in file(".")).enablePlugins(PlayJava)

libraryDependencies ++= Seq(
    guice,
    "com.rabbitmq" % "amqp-client" % "4.2.0",
    "org.mockito" % "mockito-core" % "2.10.0" % "test"
)

val nexusHost = System.getenv("NEXUS_HOST")
val nexusUser = System.getenv("NEXUS_USER")
val nexusPassword = System.getenv("NEXUS_PASSWORD")

publishTo := {
    val httpNexus = "http://" + nexusHost
    if (version.value.trim.endsWith("SNAPSHOT"))
        Some("snapshots" at httpNexus + "/repository/maven-snapshots")
    else
        Some("releases" at httpNexus + "/repository/maven-releases")
}

credentials += Credentials("Sonatype Nexus Repository Manager", nexusHost, nexusUser, nexusPassword)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And, then publishing becomes super easy, we only need to run &lt;code&gt;sbt publish&lt;/code&gt; and voilà! The module is published inside our own repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using it in your Project
&lt;/h2&gt;

&lt;p&gt;Now comes the second part, actually using your module in your project. For this, theoretically, all you need is to add a Library Dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;libraryDependencies ++= Seq(
    "my.organization" % "some-module" % "1.0.0-SNAPSHOT",
    "com.rabbitmq" % "amqp-client" % "4.2.0"
)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But this won't be enough.&lt;/p&gt;

&lt;p&gt;First of all, you need to add a resolver to specify the new repository for your module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resolvers += "My Snapshots" at "https://my.url/repository/maven-snapshots/"

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will also need your credentials to access it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;credentials += Credentials("Sonatype Nexus Repository Manager", "my.url", nexusUser, nexusPassword)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now you are all excited and you type in &lt;code&gt;sbt clean update&lt;/code&gt;... And it fails miserably.&lt;/p&gt;

&lt;p&gt;Now, the thing is, SBT is gonna try to find your module at &lt;code&gt;https://my.url/repository/maven-snapshots/my/organization/some-module/1.0.0-SNAPSHOT/...&lt;/code&gt;. But if you look carefully in your Nexus the module is actually at &lt;code&gt;/my/organization/some-module_2.12/1.0.0-SNAPSHOT/...&lt;/code&gt;. Notice this &lt;code&gt;2.12&lt;/code&gt;? This is your Scala version, and this is actually quite important. So now, you're left with 3 choices.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remove the cross path by doing &lt;code&gt;crossPaths := false&lt;/code&gt; (highly &lt;strong&gt;NOT RECOMMENDED&lt;/strong&gt; )&lt;/li&gt;
&lt;li&gt;Specify the Scala version by depending on &lt;code&gt;some-module_2.12&lt;/code&gt; instead of just &lt;code&gt;some-module&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Using the &lt;code&gt;%%&lt;/code&gt; syntax to let SBT append this Scala version, which is the solution we chose&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So here is now our final Library Dependencies declaration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;libraryDependencies ++= Seq(
    "my.organization" %% "some-module" % "1.0.0-SNAPSHOT",
    "com.rabbitmq" % "amqp-client" % "4.2.0"
)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the complete file for science:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name := """my_service"""
organization := "my.organization"
version := "1.0"
packageName in Universal := "my_service"

lazy val root = (project in file(".")).enablePlugins(PlayJava)

scalaVersion := "2.12.2"

val nexusUser = System.getenv("NEXUS_USER")
val nexusPassword = System.getenv("NEXUS_PASSWORD")

resolvers += "My Snapshots" at "https://my.url/repository/maven-snapshots/"
credentials += Credentials("Sonatype Nexus Repository Manager", "my.url", nexusUser, nexusPassword)

libraryDependencies ++= Seq(
  guice,
  "my.organization" %% "some-module" % "1.0.0-SNAPSHOT",
  "com.rabbitmq" % "amqp-client" % "4.0.2"
)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please note that the Scala version that is going to be appended is the one minor one of your &lt;code&gt;scalaVersion&lt;/code&gt; declaration, so here &lt;code&gt;2.12&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You're now good to go, you can get back to your fun with Play!.&lt;/p&gt;

</description>
      <category>java</category>
      <category>playframework</category>
      <category>sbt</category>
    </item>
  </channel>
</rss>
