<?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>DEV Community: Shan Desai</title>
    <description>The latest articles on DEV Community by Shan Desai (@shandesai).</description>
    <link>https://dev.to/shandesai</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%2F603350%2F4080e5a0-5888-4f95-b2e3-281fea92a137.jpeg</url>
      <title>DEV Community: Shan Desai</title>
      <link>https://dev.to/shandesai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shandesai"/>
    <language>en</language>
    <item>
      <title>Deep Dive with Ansible: Patching an Ansible Collection</title>
      <dc:creator>Shan Desai</dc:creator>
      <pubDate>Fri, 19 Apr 2024 12:26:13 +0000</pubDate>
      <link>https://dev.to/shandesai/deep-dive-with-ansible-patching-an-ansible-collection-4e8m</link>
      <guid>https://dev.to/shandesai/deep-dive-with-ansible-patching-an-ansible-collection-4e8m</guid>
      <description>&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;Currently, I have been developing a lot with Ansible at work for Automation logic&lt;br&gt;
for servers and industrial hardware. It is also a fantastic tool to performing testing&lt;br&gt;
of provisioned systems. One such test was checking whether a Grafana container was configured&lt;br&gt;
correctly through a Docker Compose project. The Grafana container was behind a Traefik reverse-proxy&lt;br&gt;
configured with TLS-termination for some internal self-signed certificates, where it wasn't required&lt;br&gt;
to check certificate validation.&lt;/p&gt;

&lt;p&gt;There exists a nice &lt;a href="https://github.com/ansible-collections/community.grafana"&gt;&lt;code&gt;community.grafana&lt;/code&gt;&lt;/a&gt; Ansible Collection that can help do lookup of some&lt;br&gt;
provisioned Grafana Dashboards.&lt;/p&gt;

&lt;p&gt;The usage based on the Documentation was quite simple:&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="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;Gather Information about current Grafana Dashboards&lt;/span&gt;
  &lt;span class="na"&gt;ansible.builtin.set_fact&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;existing_grafana_dashboards&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;{{ lookup(&lt;/span&gt;
        &lt;span class="s"&gt;'community.grafana.grafana_dashboard',&lt;/span&gt;
        &lt;span class="s"&gt;'grafana_url=https://{{ ansible_host }}/grafana grafana_user=admin grafana_password={{ grafana_admin_pass }} search=fooDash'&lt;/span&gt;
      &lt;span class="s"&gt;}}&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;ansible.builtin.debug&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;var&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;existing_grafana_dashboards&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This plugin would simply connect to each Grafana dashboard instance in my inventory and try to search for existence of dashboards&lt;br&gt;
with name &lt;code&gt;fooDash&lt;/code&gt; and store them in a local Ansible Fact for the Playbook called &lt;code&gt;existing_grafana_dashboards&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This could have worked out of the box, but as it turned out since the URL was HTTPS and my instance was hosted with a self-signed&lt;br&gt;
certificate, I got URI verification failures, which is quite common when working with Ansible and URIs.&lt;/p&gt;

&lt;p&gt;Instincts told me to just add a &lt;code&gt;validate_certs=false&lt;/code&gt; parameter to the lookup plugin. So a refactor would look something 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="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;Gather Information about current Grafana Dashboards&lt;/span&gt;
  &lt;span class="na"&gt;ansible.builtin.set_fact&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;existing_grafana_dashboards&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;{{ lookup(&lt;/span&gt;
        &lt;span class="s"&gt;'community.grafana.grafana_dashboard',&lt;/span&gt;
        &lt;span class="s"&gt;'grafana_url=https://{{ ansible_host }}/grafana grafana_user=admin grafana_password={{ grafana_admin_pass }} search=fooDash'&lt;/span&gt;
        &lt;span class="s"&gt;validate_certs=false&lt;/span&gt;
      &lt;span class="s"&gt;}}&lt;/span&gt;
 &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;ansible.builtin.debug&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;var&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;existing_grafana_dashboards&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However it turns out I still got the same SSL verification error and upon inspecting the Upstream code, the plugin did not have an&lt;br&gt;
implementation for the &lt;code&gt;validate_certs&lt;/code&gt; logic, which is quite common in Ansible's URI lookup plugin.&lt;/p&gt;

&lt;p&gt;With a proper &lt;a href="https://github.com/ansible-collections/community.grafana/issues/346"&gt;Issue report&lt;/a&gt; at the collection's repository, I tried to figure out how to patch this requirement to the lookup plugin.&lt;/p&gt;
&lt;h2&gt;
  
  
  Local Setup for Ansible Collections
&lt;/h2&gt;

&lt;p&gt;Based on the Repository's documentation, the easiest way would be to have the collection cloned in to the &lt;code&gt;COLLECTIONS_PATH&lt;/code&gt; on a work-machine&lt;br&gt;
and have the collection reflect in a project to test the changes out.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://docs.ansible.com/ansible/latest/reference_appendices/config.html#collections-paths"&gt;&lt;code&gt;COLLECTIONS_PATH&lt;/code&gt;&lt;/a&gt; is an Ansible Configuration which points to a directory where all other collections exists. If not explicitly set&lt;br&gt;
it can vary from where the collections exists in the system.&lt;/p&gt;

&lt;p&gt;If Ansible is installed via &lt;code&gt;pip install --user ansible&lt;/code&gt; then chances are the path to the collections is&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;~/.local/lib/python&amp;lt;version&amp;gt;/site-packages/ansible_collections
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If Ansible is installed globally either via some distro package manager it might be somewhere in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/usr/local/lib/python&amp;lt;version&amp;gt;/dist-packages/ansible_collections
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;TIP: Use &lt;code&gt;ansible-galaxy collection list&lt;/code&gt; to figure out where are the collections pointed at when using ansible.&lt;br&gt;
The first line shows you the path where the collections are located on the machine.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Since &lt;code&gt;community.grafana&lt;/code&gt; already exists on the work machine, it would be wise not to clone the repository in the same path&lt;br&gt;
as where all the collections already exist.&lt;/p&gt;
&lt;h3&gt;
  
  
  Overriding the Collections Path for Development
&lt;/h3&gt;

&lt;p&gt;If we read the documentation for the &lt;code&gt;COLLECTIONS_PATH&lt;/code&gt; again, it mentions:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;if COLLECTIONS_PATHS includes &lt;code&gt;{{ ANSIBLE_HOME ~ "/collections" }}&lt;/code&gt;, and you want to add my.collection to that directory,&lt;br&gt;
it must be saved as &lt;code&gt;{{ ANSIBLE_HOME} ~ "/collections/ansible_collections/my/collection" }}&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here &lt;code&gt;ANSIBLE_HOME&lt;/code&gt; would be &lt;code&gt;~/.ansible&lt;/code&gt; directory. So in my case, it would be the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;~/.ansible/collections/ansible_collections/community/grafana
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So here are the steps to setting up the override logic for the Ansible Collection logic locally:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a fork of the upstream repository on GitHub&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create the base collections directory if it doesn't exist:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/.ansible/collections/ansible_collections
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Clone to fork in such a way that the contents of the repo are placed under &lt;code&gt;ansible_collections/community/grafana&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone &amp;lt;FORK_URL&amp;gt; ~/.ansible/collections/ansible_collections/community/grafana
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;this could be generally described for other ansible collections as follows:&lt;br&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   git clone &amp;lt;URL&amp;gt; ~/.ansible/collections/ansible_collections/&amp;lt;namespace&amp;gt;/&amp;lt;collection&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can make changes to the codebase in this particular repository and test it somewhere else locally to see if the changes&lt;br&gt;
work as expected.&lt;/p&gt;
&lt;h3&gt;
  
  
  Setting Up a Local Test Base
&lt;/h3&gt;

&lt;p&gt;In order to test the changes create a simple directory where your development codebases exist e.g. &lt;code&gt;~/Development/ansible/grafana_patch&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In the particular directory add the following Ansible configuration file called &lt;code&gt;ansible.cfg&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[defaults]&lt;/span&gt;
&lt;span class="py"&gt;collections_path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;~/.ansible/collections/ansible_collections&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a local override to tell ansible, that add the collections that now exist in our &lt;code&gt;~/.ansible/collections/ansible_collections&lt;/code&gt;&lt;br&gt;
for the local project too.&lt;/p&gt;

&lt;p&gt;This is verified by performing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;
/home/User/Development/ansible/grafana_patch
&lt;span class="nv"&gt;$ &lt;/span&gt;ansible-galaxy collection list
&lt;span class="c"&gt;# /home/User/.ansible/collections/ansible_collections&lt;/span&gt;
Collection        Version
&lt;span class="nt"&gt;-----------------&lt;/span&gt; &lt;span class="nt"&gt;-------&lt;/span&gt;
community.grafana 1.8.0
&lt;span class="c"&gt;# /usr/local/lib/python3.8/dist-packages/ansible_collections&lt;/span&gt;
Collection                    Version
&lt;span class="nt"&gt;-----------------------------&lt;/span&gt; &lt;span class="nt"&gt;-------&lt;/span&gt;
&lt;span class="c"&gt;### Other collections omitted for brewity&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now a simple playbook called &lt;code&gt;test_dashboard_lookup.yml&lt;/code&gt;&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;localhost&lt;/span&gt;
  &lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;ansible.builtin.set_fact&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;local_dashboard&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;{{ lookup(&lt;/span&gt;
            &lt;span class="s"&gt;'community.grafana.grafana_dashboard',&lt;/span&gt;
            &lt;span class="s"&gt;'grafana_url=https://localhost:3000 grafana_user=admin grafana_password=admin search=foo',&lt;/span&gt;
            &lt;span class="s"&gt;validate_certs=false&lt;/span&gt;
          &lt;span class="s"&gt;}}&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;ansible.builtin.debug&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;var&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local_dashboards&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;can be used to verify if things are working locally with a default Grafana Container setup with self-signed certs using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-playbook test_dashboard_lookup.yml &lt;span class="nt"&gt;-vvvv&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Result
&lt;/h2&gt;

&lt;p&gt;Based on the &lt;a href="https://github.com/ansible-collections/community.grafana/issues/346"&gt;Issue&lt;/a&gt; filed by me, there now exists &lt;a href="https://github.com/ansible-collections/community.grafana/pull/356"&gt;a patch&lt;/a&gt; that provides the necessary feature for validation / not validating&lt;br&gt;
SSL certificates.&lt;/p&gt;

&lt;p&gt;New Tool, New Environment, New way to Patch something =&amp;gt; Better to document!&lt;/p&gt;

</description>
      <category>ansible</category>
      <category>devops</category>
    </item>
    <item>
      <title>Forward Compatibility for Mosquitto MQTT Broker with Docker Compose v2</title>
      <dc:creator>Shan Desai</dc:creator>
      <pubDate>Mon, 23 Oct 2023 20:36:08 +0000</pubDate>
      <link>https://dev.to/shandesai/forward-compatibility-for-mosquitto-mqtt-broker-with-docker-compose-v2-3n48</link>
      <guid>https://dev.to/shandesai/forward-compatibility-for-mosquitto-mqtt-broker-with-docker-compose-v2-3n48</guid>
      <description>&lt;h1&gt;
  
  
  Planning
&lt;/h1&gt;

&lt;p&gt;While working on a personal project &lt;a href="https://github.com/shantanoo-desai/komponist"&gt;Komponist&lt;/a&gt;, I was due to update &lt;br&gt;
&lt;a href="https://github.com/eclipse/mosquitto"&gt;Mosquitto MQTT Broker&lt;/a&gt; due to a &lt;a href="https://github.com/eclipse/mosquitto/blob/v2.0.18/ChangeLog.txt#L27"&gt;CVE&lt;/a&gt; and found some interesting changes&lt;br&gt;
that will impact me in the future when it comes to configuring the Broker. This post&lt;br&gt;
provides a solution to make the Broker compatible with future versions using new and&lt;br&gt;
less visited concepts in &lt;strong&gt;Docker Compose v2&lt;/strong&gt;, namely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docker Init Containers&lt;/li&gt;
&lt;li&gt;Using the &lt;code&gt;include&lt;/code&gt; feature in Docker Compose files&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Observations
&lt;/h2&gt;

&lt;p&gt;This section provides information on what currently is observed post upgrading the&lt;br&gt;
Mosquitto Broker to &lt;strong&gt;v2.0.18&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Current Docker Compose File
&lt;/h3&gt;

&lt;p&gt;This is my current &lt;code&gt;docker-compose.mosquitto.yml&lt;/code&gt; with the following file structure &lt;br&gt;
generated by &lt;strong&gt;Komponist&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;|- docker-compose.mosquitto.yml
|-- mosquitto
    |--- acl
    |--- &lt;span class="nb"&gt;users&lt;/span&gt;
    |--- mosquitto.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Compose file content is:&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="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;mosquitto&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker.io/eclipse-mosquitto:2.0.18&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;komponist_mosquitto&lt;/span&gt;
    &lt;span class="na"&gt;configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mosquitto_conf&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mosquitto_acl&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mosquitto_users&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mosquitto -c /mosquitto_conf&lt;/span&gt;
    &lt;span class="na"&gt;security_opt&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;no-new-privileges=true"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/etc/timezone:/etc/timezone:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/etc/localtime:/etc/localtime:ro&lt;/span&gt;

&lt;span class="na"&gt;configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;mosquitto_conf&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./mosquitto/mosquitto.conf&lt;/span&gt;
  &lt;span class="na"&gt;mosquitto_acl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./mosquitto/acl&lt;/span&gt;
  &lt;span class="na"&gt;mosquitto_users&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./mosquitto/users&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bringing the container up using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.mosquitto.yml up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The logs will throw some warnings such as the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Warning: File /mosquitto_users has world readable permissions. Future versions will refuse to load this file.

Warning: File /mosquitto_users owner is not mosquitto. Future versions will refuse to load this file.
...
...

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

&lt;/div&gt;



&lt;p&gt;So for future versions the files need to have a specific user and file permissions&lt;br&gt;
to make the current configuration files valid.&lt;/p&gt;
&lt;h3&gt;
  
  
  Caveats
&lt;/h3&gt;

&lt;p&gt;In systems where I do not have superuser privileges to create a new user &lt;code&gt;mosquitto&lt;/code&gt; whose&lt;br&gt;
UID/GID should be &lt;code&gt;1883&lt;/code&gt; and changing file permissions is not an option, the only way to tackle&lt;br&gt;
such a solution is through Docker Init Containers.&lt;/p&gt;

&lt;p&gt;Docker Init Containers are like sidecar containers in Kubernetes or a patching container where&lt;br&gt;
it initializes a state (here specific configuration file) and then starts the core container.&lt;/p&gt;

&lt;p&gt;This feature has existed for a while, but it is not well documented in the Compose Specifications&lt;br&gt;
with some practical examples. In essence, we will use the &lt;code&gt;depends_on&lt;/code&gt; service dependency logic &lt;br&gt;
between a generic &lt;code&gt;alpine&lt;/code&gt; container that will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;change the user for the core configuration files&lt;/li&gt;
&lt;li&gt;change the file permissions for the core configuration files&lt;/li&gt;
&lt;li&gt;pass on the core files to the &lt;code&gt;eclipse-mosquitto&lt;/code&gt; broker using shared volumes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once this container is mounted with the files and is run successfully, the adapted configuration&lt;br&gt;
files will be passed on to the main docker container for the Mosquitto MQTT Broker.&lt;/p&gt;
&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;We keep the file structure same as previously mentioned but we create a new Compose file&lt;br&gt;
called &lt;code&gt;docker-compose.mosquitto-init.yml&lt;/code&gt; file as follows:&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="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;mosquitto_init&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;alpine:3.18&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mosquitto_init&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sh -c 'chmod 0400 -R /config/ &amp;amp;&amp;amp; chown 1883:1883 -R /config/'&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./mosquitto/mosquitto.conf:/config/mosquitto_conf&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./mosquitto/users:/config/mosquitto_users&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./mosquitto/acl:/config/mosquitto_acl&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are mounting the configuration files into the init container under the &lt;code&gt;/config&lt;/code&gt; directory&lt;br&gt;
and changing the file persmissions and ownership for the files in this init container.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;NOTE: we will have to refactor out the Docker Configs section previously configured in&lt;br&gt;
original &lt;code&gt;docker-compose.mosquitto.yml&lt;/code&gt; file into the &lt;code&gt;docker-compose.mosquitto-init.yml&lt;/code&gt; file&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We now refactor the &lt;code&gt;mosquitto.conf&lt;/code&gt; file a bit to accept the new location for the files&lt;br&gt;
as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# MQTT Port Listener
listener    1883
protocol    mqtt

# Authentication
allow_anonymous     false
password_file       /config/mosquitto_users

# Authorization
acl_file    /config/mosquitto_acl
# Logging Configuration
log_timestamp true
log_type all
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the &lt;code&gt;/config&lt;/code&gt; as prefix to the ACL and password file.&lt;/p&gt;

&lt;p&gt;Final refactoring takes place in the &lt;code&gt;docker-compose.mosquitto.yml&lt;/code&gt; file as follows:&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="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;docker-compose.mosquitto-init.yml&lt;/span&gt;

&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;mosquitto&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker.io/eclipse-mosquitto:2.0.18&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;komponist_mosquitto&lt;/span&gt;
    &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mosquitto -c /config/mosquitto_conf&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;mosquitto_init&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;service_completed_successfully&lt;/span&gt;
    &lt;span class="na"&gt;security_opt&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;no-new-privileges=true"&lt;/span&gt;
    &lt;span class="na"&gt;volumes_from&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mosquitto_init&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/etc/timezone:/etc/timezone:ro&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/etc/localtime:/etc/localtime:ro&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Upon bringing the stack up again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nt"&gt;-f&lt;/span&gt; docker-compose.mosquitto.yml up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the Warnings should be gone and your config files that were valid prior to&lt;br&gt;
version 2.0.18 will still be compatible for the upcoming versions (unless the development&lt;br&gt;
of mosquitto demands something more in the future).&lt;/p&gt;

&lt;p&gt;In a Nutshell, the main &lt;code&gt;mosquitto&lt;/code&gt; service is now dependent on the &lt;code&gt;mosquitto-init&lt;/code&gt; service&lt;br&gt;
via the &lt;code&gt;depends_on&lt;/code&gt; spec and core files are exchanged from the init container to the main container&lt;br&gt;
via &lt;code&gt;volumes_from&lt;/code&gt; spec.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;NOTE: this approach will change the file permissions / owner for the core configuration files&lt;br&gt;
on the host machine since they are volume mounted.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;At the time of writing this post, this is the only approach I have tested out.&lt;/p&gt;

&lt;p&gt;If you have read this post and have better approaches in mind, connect with me on LinkedIn and share&lt;br&gt;
your solutions or improve upon the method. I would love to hear your feedback.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Gist
&lt;/h2&gt;

&lt;p&gt;the Gist of the code can be found &lt;a href="https://gist.github.com/shantanoo-desai/1634f3206ce486707a4c047bcc043c4c"&gt;here&lt;/a&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>mqtt</category>
    </item>
    <item>
      <title>Generating Mosquitto MQTT Broker Credentials with Ansible</title>
      <dc:creator>Shan Desai</dc:creator>
      <pubDate>Wed, 16 Aug 2023 11:37:23 +0000</pubDate>
      <link>https://dev.to/shandesai/generating-mosquitto-mqtt-broker-credentials-with-ansible-2o5a</link>
      <guid>https://dev.to/shandesai/generating-mosquitto-mqtt-broker-credentials-with-ansible-2o5a</guid>
      <description>&lt;h2&gt;
  
  
  Challenge
&lt;/h2&gt;

&lt;p&gt;This is a development note from a personal, open-source project called &lt;a href="https://github.com/shantanoo-desai/komponist"&gt;&lt;strong&gt;Komponist&lt;/strong&gt;&lt;/a&gt;.&lt;br&gt;
The aim of the project is to provide IoT / IIoT Developers a platform that can be used locally as well as in production using commonly used containers &lt;br&gt;
in the Edge computing space such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Time-series Databases (InfluxDBv1/v2, QuestDB, TimescaleDB)&lt;/li&gt;
&lt;li&gt;Data processing tools (Node-RED, Telegraf)&lt;/li&gt;
&lt;li&gt;Dashboard visualization / monitoring (Grafana)&lt;/li&gt;
&lt;li&gt;Message Broker (Eclipse-Mosquitto)
There are standard tools / software used quite ubiquitously with containerization technology like &lt;strong&gt;Docker / Compose v2&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One major challenge that developers face is to maintain a lot of files that are needed to bring these tools / software up on initialization. &lt;br&gt;
These files mostly contain user credentials, initialization scripts and configurations needed to start these containers correctly and according to user specifications.&lt;br&gt;
Additionally, each container of these tools / software are very distinct and may rely on various setups, markup languages (YAML, TOML, INI) etc.&lt;br&gt;
and there is rarely any coherence available between one tool against the other.&lt;/p&gt;

&lt;p&gt;Komponist solves this issue by add a logical configuration generation tool over the tools and relies only on two core files&lt;br&gt;
that describes the complete stack along side the respective credentials and configurations. &lt;br&gt;
It uses the easy to use &lt;code&gt;ansible&lt;/code&gt; configuration management tool to generate the respective docker-compose files, credentials file, YAML and TOML files for the software&lt;br&gt;
and prepares the stack within a couple of minutes either locally or within a group of IoT edge devices.&lt;/p&gt;

&lt;p&gt;This writeup focuses more on the Mosquitto MQTT Broker. The writeup describes how to leverage Ansible and Jinja2 powered Templating engine to generate Mosquitto Broker's&lt;br&gt;
credentials.&lt;/p&gt;

&lt;p&gt;NOTE: This writeup requires some basic to intermediate knowledge of &lt;a href="https://ansible.com"&gt;Ansible&lt;/a&gt; as a tool.&lt;/p&gt;
&lt;h2&gt;
  
  
  Mosquitto Broker's Credentials Generation
&lt;/h2&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;mosquitto_passwd&lt;/code&gt; CLI native
&lt;/h3&gt;

&lt;p&gt;According to the &lt;a href="https://mosquitto.org/man/mosquitto_passwd-1.html"&gt;mosquitto broker's authentication methods documentation&lt;/a&gt;&lt;br&gt;
it is possible to generate password files when one either has the &lt;code&gt;mosquitto_passwd&lt;/code&gt; CLI installed locally / remotely on the IoT devices.&lt;br&gt;
For example, the following plain-text &lt;code&gt;passwd.txt&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user1:superSecretPass1
user2:superSecretPass2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;will encrypt the plain-text passwords into hashes that are acceptable by the mosquitto broker via the &lt;code&gt;mosquitto-passwd&lt;/code&gt; CLI using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mosquitto_passwd &lt;span class="nt"&gt;-U&lt;/span&gt; /path/to/passwd.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, it relies on the existence of &lt;code&gt;mosquitto_passwd&lt;/code&gt; tool to be available locally.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;mosquitto_passwd&lt;/code&gt; CLI via Docker Container
&lt;/h3&gt;

&lt;p&gt;The same tool can be used via a Docker container where the plain-text password &lt;code&gt;passwd.txt&lt;/code&gt; file can be encrypted via spinning a docker container&lt;br&gt;
locally and mounting the plain-text file to a dedicated path into it. An example with the same &lt;code&gt;passwd.txt&lt;/code&gt; can be:&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;-v&lt;/span&gt; /path/to/passwd.txt:/mosquitto/config/users &lt;span class="se"&gt;\&lt;/span&gt;
eclipse-mosquitto:2.0.15 &lt;span class="se"&gt;\&lt;/span&gt;
mosquitto_passwd &lt;span class="nt"&gt;-U&lt;/span&gt; /mosquitto/config/users
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since the file is mounted into the container, any changes within the container will be reflected on the &lt;code&gt;passwd.txt&lt;/code&gt; on the host i.e., the plain-text passwords will be encrypted accordingly.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Both these methods, require dependency on either the &lt;code&gt;mosquitto_passwd&lt;/code&gt; CLI or the &lt;code&gt;eclipse-mosquitto&lt;/code&gt; Docker Image to exist locally beforehand.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Independent Credential Generation for Broker
&lt;/h2&gt;

&lt;p&gt;Upon observing the code base for &lt;a href="https://github.com/eclipse/mosquitto/blob/master/src/password_mosq.h"&gt;eclipse/mosquitto&lt;/a&gt; it turns out the password hash generation relies on two methods:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;SHA512 + additional salting and base64 encryption logic&lt;/li&gt;
&lt;li&gt;SHA512 type PBKDF2 encryption logic&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The details of the these two methods are beyond the scope of the write up but the implementations for them are quite common and can be found in every programming language.&lt;/p&gt;

&lt;p&gt;Since &lt;strong&gt;Komponist&lt;/strong&gt; relies on Ansible and Jinja2 templating engine which are directly reliant on Python3.x as a programming language, the implementation is rather trivial thanks to &lt;a href="https://passlib.readthedocs.io/en/stable/index.html"&gt;&lt;code&gt;passlib&lt;/code&gt;&lt;/a&gt; python package.&lt;br&gt;
&lt;code&gt;passlib&lt;/code&gt; provides the implementation of &lt;strong&gt;PBKDF2_SHA512&lt;/strong&gt; logic with all the necessary encoding logic needed for the hashing of plain-text passwords that are acceptable by the Broker.&lt;/p&gt;
&lt;h3&gt;
  
  
  Templates for Password File
&lt;/h3&gt;

&lt;p&gt;As previously seen a &lt;code&gt;passwd.txt&lt;/code&gt; an acceptable structure is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user1:password1
user2:password2
userN:passwordN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;users.j2&lt;/code&gt; template file can be created as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{%- for user in mosquitto.users %}
{{ user.username }}:{{ user.password }}
{% endfor %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where the &lt;code&gt;mosquitto.users&lt;/code&gt; is an array of dictionaries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mosquitto:
  users:
    - username: user1
      password: password1
    - username: user2
      password: password2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To test this logic we can create an Ansible Playbook  called &lt;code&gt;mosquitto_users.yml&lt;/code&gt; as follows:&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="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;Mosquitto Users (plain-text) file generation&lt;/span&gt;
  &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;localhost&lt;/span&gt;
  &lt;span class="na"&gt;gather_facts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&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;mosquitto&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;users&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;testuser1&lt;/span&gt;
          &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;password1&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;testuser2&lt;/span&gt;
          &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;password2&lt;/span&gt;

  &lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="pi"&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;generate the Mosquitto users file&lt;/span&gt;
      &lt;span class="na"&gt;ansible.builtin.template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;users.j2&lt;/span&gt;
        &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;users&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Execute the playbook using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;tree
├── mosquitto_users.yml
└── users.j2
&lt;span class="nv"&gt;$ &lt;/span&gt;ansible-playbook mosquitto_users.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;upon execution of the playbook a &lt;code&gt;users&lt;/code&gt; file will be generated that will fill the values of the credentials accordingly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom Jinja2 Filter for Hash Generation
&lt;/h3&gt;

&lt;p&gt;The template file &lt;code&gt;users.j2&lt;/code&gt; currently fulfills the criteria of structure required for the Broker.&lt;br&gt;
We now create a custom Jinja2 filter that will consume the plain-text passwords and generate the acceptable hashes for the Broker.&lt;/p&gt;

&lt;p&gt;A Jinja2 filter in Ansible is rather a very simple python script that Ansible executes to generate the respective logic.&lt;br&gt;
As previously mentioned, we know that &lt;code&gt;passlib&lt;/code&gt; library provides a &lt;a href="https://passlib.readthedocs.io/en/stable/lib/passlib.hash.pbkdf2_digest.html"&gt;&lt;strong&gt;PBKDF2_SHA512&lt;/strong&gt;_&lt;/a&gt;&lt;br&gt;
implementation and upon inspecting the code base for eclipse mosquitto we can also determine how many rounds(iterations) are required and what the salt length should be.&lt;br&gt;
The values are as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Iterations are 101&lt;/li&gt;
&lt;li&gt;salt length is 12 bytes (random bytes)
Based on the &lt;code&gt;passlib&lt;/code&gt; API this information can be passed into a function call rather easily.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's call the filter the same as &lt;code&gt;mosquitto_passwd&lt;/code&gt;  . Here is the implementation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a folder called &lt;code&gt;filter_plugins&lt;/code&gt; and create &lt;code&gt;mosquitto_passwd.py&lt;/code&gt; under it&lt;/li&gt;
&lt;li&gt;install the &lt;code&gt;passlib&lt;/code&gt; package using &lt;code&gt;pip install --user passlib&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Add the following code into the &lt;code&gt;mosquitto_passwd.py&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;ansible.errors&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AnsibleError&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mosquitto_passwd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;passwd&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;passlib.hash&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;AnsibleError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'mosquitto_passlib custom filter requires the passlib pip package installed'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;SALT_SIZE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;
    &lt;span class="n"&gt;ITERATIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;101&lt;/span&gt;

    &lt;span class="n"&gt;digest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;passlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pbkdf2_sha512&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;using&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;salt_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SALT_SIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rounds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ITERATIONS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; \
                                        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;passwd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; \
                                        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pbkdf2-sha512"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"7"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; \
                                        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"+"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;digest&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"=="&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FilterModule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;'mosquitto_passwd'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mosquitto_passwd&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;Few things to note in the implementation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hash digest generated by &lt;code&gt;passlib.hash.pbkdf2_sha512&lt;/code&gt; is the of the following structure:
&lt;code&gt;$pbkdf2-sha256$6400$0ZrzXitFSGltTQnBWOsdAw$Y11AchqV4b0sUisdZd0Xr97KWoymNE0LNNrnEgY4H9M&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Mosquitto Broker requires that the first part of the digest either be &lt;code&gt;7&lt;/code&gt; for PBKDF2_SHA512 or &lt;code&gt;6&lt;/code&gt; for the SHA-512. This is done by the &lt;code&gt;.replace("pbkdf2_sha512", "7")&lt;/code&gt; in the code&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;passlib&lt;/code&gt; PBKDF2_SHA512 implementation has a &lt;a href="https://passlib.readthedocs.io/en/stable/lib/passlib.utils.binary.html#passlib.utils.binary.ab64_encode"&gt;shortened base64 format&lt;/a&gt; which omits padding and whitespaces, which will not be acceptable by the Broker.
In order to make the digest compatible we replace the instances of &lt;code&gt;.&lt;/code&gt; with &lt;code&gt;+&lt;/code&gt;  in the digest (via &lt;code&gt;.replace(".", "+")&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;we add the &lt;code&gt;==&lt;/code&gt; characters at the end of the digest to make up for the shortened base64 encoding implementation when it comes to the overall length of the digest&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The final step should is to add the custom &lt;code&gt;mosquitto_passwd&lt;/code&gt; filter into our &lt;code&gt;users.j2&lt;/code&gt; file as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{%- for user in mosquitto.users %}
{{ user.username }}:{{ user.password | mosquitto_passwd }}
{% endfor %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the template during generation will pass the &lt;code&gt;user.password&lt;/code&gt; to the &lt;code&gt;mosquitto_passwd&lt;/code&gt; filter and provide the hashed value as opposed to the plain-text values in a &lt;code&gt;users&lt;/code&gt; file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;For the following structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;tree &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;span class="nb"&gt;.&lt;/span&gt;
├── filter_plugins
│   └── mosquitto_passwd.py
├── mosquitto_users.yml
└── users.j2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;executing the playbook &lt;code&gt;mosquitto_users.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible-playbook mosquitto_users.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;will generate a &lt;code&gt;users&lt;/code&gt; file in the directory. Upon viewing the contents of the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat users
&lt;/span&gt;testuser1:&lt;span class="nv"&gt;$7$101$9h4jJKT0HiMEgDAm$3ynyYne&lt;/span&gt;/t4KJyjDqOyCVzF0G0Oa71nu0K9iaa1NNfzCNc5c71iVnUVqV+zxDjGkaDy6gSAWAyk6KmqgTaE1IDQ&lt;span class="o"&gt;==&lt;/span&gt;
testuser2:&lt;span class="nv"&gt;$7$101$HwNAKGVsjbE2JkRI$Nk&lt;/span&gt;+xnkjOTyllT7hD4N5kd9kGarMAQJMJVgWraa0VxmKZgyRyfUjG0+kUCPE1ZuLaGPrbeq0H80Pl6tHd+Qm2Lw&lt;span class="o"&gt;==&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(password hashes may vary, since salt lengths are randomly generated)&lt;/p&gt;

&lt;h3&gt;
  
  
  Verifying with the Broker
&lt;/h3&gt;

&lt;p&gt;The best way to confirm that the hashes are acceptable is to generate a &lt;code&gt;mosquitto.conf&lt;/code&gt; file with the following contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# MQTT Port Listener
listener    1883
protocol    mqtt

# Authentication
allow_anonymous     false
password_file       /mosquitto_users
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then using the following &lt;code&gt;docker-compose.yml&lt;/code&gt; file:&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="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;mosquitto&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker.io/eclipse-mosquitto:2.0.15&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test_broker&lt;/span&gt;
    &lt;span class="na"&gt;configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mosquitto_conf&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mosquitto_users&lt;/span&gt;
    &lt;span class="na"&gt;entrypoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mosquitto -c /mosquitto_conf&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;1883:1883&lt;/span&gt;
&lt;span class="na"&gt;configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;mosquitto_conf&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./mosquitto.conf&lt;/span&gt;
  &lt;span class="na"&gt;mosquitto_users&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./users&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;upon performing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will be able to see the logs of the broker without any erroneous information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test_broker  | 1692181531: mosquitto version 2.0.15 starting
test_broker  | 1692181531: Config loaded from /mosquitto_conf.
test_broker  | 1692181531: Opening ipv4 listen socket on port 1883.
test_broker  | 1692181531: Opening ipv6 listen socket on port 1883.
test_broker  | 1692181531: mosquitto version 2.0.15 running
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use an MQTT client and try to connect to the broker &lt;code&gt;&amp;lt;IP_ADDRESS&amp;gt;:1883&lt;/code&gt;  with either of the credentials values mentioned in &lt;code&gt;mosquitto.users&lt;/code&gt; and it should work.&lt;/p&gt;

&lt;p&gt;Now you have a functioning configuration management tool for your eclipse Mosquitto Broker using a popular DevOps / Configuration Management Tool (Ansible) &lt;br&gt;
which provides you a guarantee of authentication without having any Mosquitto related software installed beforehand on your IoT Devices / Servers / Industrial Hardware.&lt;/p&gt;

&lt;h2&gt;
  
  
  Potential Extensions
&lt;/h2&gt;

&lt;p&gt;You can now generate Jinja2 based template files for the Access-Control Lists (ACLs)  or other related configurations with having to maintain multiple versions of them rather let Ansible generate them for you.&lt;/p&gt;

&lt;p&gt;Similar, logic has been developed for &lt;a href="https://github.com/shantanoo-desai/komponist"&gt;&lt;em&gt;Komponist&lt;/em&gt;&lt;/a&gt;&lt;br&gt;
where a user can define how many users and what ACL permissions each user has on topics by just maintaining a single credentials file for the Mosquitto Broker as well as other containers such as InfluxDB, node-RED etc.&lt;/p&gt;

</description>
      <category>ansible</category>
      <category>mqtt</category>
      <category>devops</category>
    </item>
    <item>
      <title>Telegraf Deployment Strategies with Docker Compose</title>
      <dc:creator>Shan Desai</dc:creator>
      <pubDate>Fri, 21 Jul 2023 07:53:45 +0000</pubDate>
      <link>https://dev.to/shandesai/telegraf-deployment-strategies-with-docker-compose-5chl</link>
      <guid>https://dev.to/shandesai/telegraf-deployment-strategies-with-docker-compose-5chl</guid>
      <description>&lt;h2&gt;
  
  
  InfluxData's Telegraf
&lt;/h2&gt;

&lt;p&gt;Telegraf is widely used as a metric aggregation tool thanks to the diverse amount of plugins it provides which interface with a multitude of systems without having to write complex software logic. With the advent of the Low-Code / No-Code paradigm in system operations; Telegraf sees itself as a front-runner.&lt;/p&gt;

&lt;p&gt;However, with Low-Code/No-Code tools it might get complicated to maintain a lot of additional yet necessary information like credentials to other subsystems. Although standard practice dictates the use of Environment Variables within Telegraf - with version 1.27 and beyond Secret Stores will come in handy when passing credentials into Telegraf plugins without having to pass environment variables explicitly, especially when using a containerization tool like Docker.&lt;/p&gt;

&lt;p&gt;We will go through some strategies of deploying a Telegraf Docker Container using Docker Compose v2 tool in this post that may help intermediate to advanced users decide upon how to organize their stack configurations appropriately&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Docker Secrets
&lt;/h3&gt;

&lt;p&gt;Docker Compose v2 specifications provide a useful &lt;a href="https://github.com/compose-spec/compose-spec/blob/master/09-secrets.md"&gt;Secrets feature&lt;/a&gt; which may also be used for standalone Compose Application Stacks and not just in Docker Swarm mode. With Docker Secrets the environment variables that contain credentials for other subsystems are mounted into the Telegraf Container as files. These secret files are read through the Docker Secret Store plugin and passed to the respective plugins in a relatively safe manner. By using the Docker Secret Store Plugin, one can also avoid credentials that were previously visible via environment variables, to be now hidden behind runtime secret files within the container.&lt;br&gt;
Standard Method with Environment Variables&lt;br&gt;
As an example, it is possible to pass the credentials to a plugin via the environment variable placeholder in a telegraf configuration file where the credentials for a plugin exist in a &lt;code&gt;.env&lt;/code&gt; file (e.g. MQTT input Plugin)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MQTT_USERNAME=telegraf
MQTT_PASSWORD=superSecurePasswordMQTT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A telegraf configuration file for the MQTT Input plugin can be as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[[inputs.mqtt_consumer]]&lt;/span&gt;

  &lt;span class="py"&gt;servers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s"&gt;"tcp://&amp;lt;broker_ip&amp;gt;:1883"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="py"&gt;topics&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s"&gt;"test1/#"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"test2/+/topic"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="py"&gt;username&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"${MQTT_USERNAME}"&lt;/span&gt;
  &lt;span class="py"&gt;password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"${MQTT_PASSWORD}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A Docker Compose File for the example can be as follows:&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="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;telegraf&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker.io/telegraf:latest&lt;/span&gt;
   &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;telegraf&lt;/span&gt;
   &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MQTT_USERNAME=${MQTT_USERNAME}&lt;/span&gt;
     &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;MQTT_PASSWORD=${MQTT_PASSWORD}&lt;/span&gt;
   &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./telegraf.conf:/etc/telegraf/telegraf.conf:ro&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the container is brought up, the values of the environment variables are interpolated by telegraf in order to connect to the MQTT Broker. This is with an assumption that the &lt;code&gt;.env&lt;/code&gt; file and the &lt;code&gt;docker-compose.yml&lt;/code&gt; file are in the same directory. This works well however a simple inspection of the running container may yield the credential values via the environment variable by a command such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nb"&gt;exec &lt;/span&gt;telegraf &lt;span class="nb"&gt;env&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OR&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker inspect &lt;span class="nt"&gt;-f&lt;/span&gt; “&lt;span class="o"&gt;{{&lt;/span&gt; .Config.Env &lt;span class="o"&gt;}}&lt;/span&gt;” &amp;lt;telegraf_container_name / telegraf_container_id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With Docker Secrets and Telegraf Docker Secret Store Plugin&lt;br&gt;
With Docker Secrets we can change the Compose file to pick the environment variables up from the &lt;code&gt;.env&lt;/code&gt; file and let Docker Compose mount these values as files under the &lt;code&gt;/run/secrets&lt;/code&gt; directory in the telegraf container as follows:&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="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker.io/telegraf:latest&lt;/span&gt;
  &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;telegraf&lt;/span&gt;
  &lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mqtt_username&lt;/span&gt;
      &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0444&lt;/span&gt;
  &lt;span class="err"&gt; &lt;/span&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mqtt_password&lt;/span&gt;
     &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0444&lt;/span&gt;
  &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
&lt;span class="s"&gt;./telegraf.conf:/etc/telegraf/telegraf.conf:ro&lt;/span&gt;

&lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
  &lt;span class="na"&gt;mqtt_username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MQTT_USERNAME&lt;/span&gt;
  &lt;span class="na"&gt;mqtt_password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MQTT_PASSWORD&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docker Compose will mount the values of &lt;code&gt;MQTT_USERNAME&lt;/code&gt; and &lt;code&gt;MQTT_PASSWORD&lt;/code&gt; into the &lt;code&gt;/run/secrets/mqtt_username&lt;/code&gt; and &lt;code&gt;/run/secrets/mqtt_password&lt;/code&gt; file within the container at runtime. We also set the file permissions to world-readable (0444) so that all users within the container may be able to read the values&lt;/p&gt;

&lt;p&gt;Similarly we adapt the telegraf configuration file with the Docker Secret Store plugin and the MQTT credentials as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="err"&gt;[[&lt;/span&gt; &lt;span class="err"&gt;inputs.mqtt_consumer&lt;/span&gt; &lt;span class="err"&gt;]]&lt;/span&gt;
  &lt;span class="py"&gt;servers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="err"&gt;“tcp://&amp;lt;broker_ip&amp;gt;:&lt;/span&gt;&lt;span class="mi"&gt;1883&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="py"&gt;topics&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s"&gt;"test1/#"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"test2/+/topic"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="py"&gt;username&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="err"&gt;“@{custom_secretstore:mqtt_username}”&lt;/span&gt;
  &lt;span class="py"&gt;password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="err"&gt;“@{custom_secretstore:mqtt_password}”&lt;/span&gt;

&lt;span class="err"&gt;[[&lt;/span&gt; &lt;span class="err"&gt;secretstores.docker&lt;/span&gt; &lt;span class="err"&gt;]]&lt;/span&gt;
  &lt;span class="py"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="err"&gt;“custom_secretstore”&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bringing the container up with &lt;code&gt;docker compose up&lt;/code&gt; will let Telegraf connect to the MQTT Broker and subscribe to the required topics.&lt;/p&gt;

&lt;p&gt;A benefit with this method is that the previously visible environment variables within the docker container are now safely mounted into the container at runtime and upon inspection via&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nb"&gt;exec &lt;/span&gt;telegraf &lt;span class="nb"&gt;env&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker inspect &lt;span class="nt"&gt;-f&lt;/span&gt; “&lt;span class="o"&gt;{{&lt;/span&gt; .Config.Env &lt;span class="o"&gt;}}&lt;/span&gt;” &amp;lt;telegraf_container_name/ telegraf_container_id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The environment variables won’t be visible and provide a bit more safe usage of our credentials.&lt;/p&gt;

&lt;h3&gt;
  
  
  Splitting Telegraf Configuration Files
&lt;/h3&gt;

&lt;p&gt;There may be situations where a Telegraf Configuration file starts to grow in size and the number of plugin configurations would be better maintained if they were to be split into individual files. Telegraf is able to consume multiple configuration plugin files and is able to process them one-by-one to run the telegraf agent as if these files were merged together (it is not a simple merge however, there is some more effort that the source code has to undertake).&lt;/p&gt;

&lt;p&gt;It is possible to mount a directory with split configuration files to the telegraf Compose service as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;telegraf/
| - inputs.&amp;lt;plugin_name#1&amp;gt;.conf
| - inputs.&amp;lt;plugin_name#2&amp;gt;.conf
| - outputs.&amp;lt;plugin_name#1&amp;gt;.conf
| - outputs.&amp;lt;plugin_name#2&amp;gt;.conf
| - processors.&amp;lt;plugin_name#1&amp;gt;.conf
| - processors.&amp;lt;plugin_name#2&amp;gt;.conf
| - secretstores.&amp;lt;plugin_name&amp;gt;.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The respective Compose file is as follows:&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="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;telegraf&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;telegraf:latest&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;telegraf&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./telegraf:/etc/telegraf/telegraf.d/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We notify telegraf that the configuration is to be picked up from a directory i.e. &lt;code&gt;/etc/telegraf/telegraf.d/&lt;/code&gt; and not a standalone configuration file here. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;NOTE: It is recommended to use the &lt;code&gt;order&lt;/code&gt; parameter in processor plugins to let telegraf know in which order the data coming in from input plugins needs to be manipulated.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Of course, the split configuration files can be used together with Docker Secrets.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker Config for Telegraf Configuration File
&lt;/h3&gt;

&lt;p&gt;There is also a possibility where a telegraf configuration file can be mounted via &lt;a href="https://github.com/compose-spec/compose-spec/blob/master/08-configs.md"&gt;Docker Configs feature&lt;/a&gt; instead of volume mounts. &lt;br&gt;
According to the &lt;a href="https://github.com/compose-spec/compose-spec/blob/master/08-configs.md#configs-top-level-element"&gt;Compose specification document&lt;/a&gt; for Docker Configs:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Configs allow services to adapt their behaviour without the need to rebuild a Docker image.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Mounting Telegraf Configurations files via Docker Config is very seldomly used in deployments with stand alone Compose applications, however they may be beneficial when working with custom Docker images for Telegraf.&lt;/p&gt;

&lt;p&gt;A common Compose file for Telegraf&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="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;telegraf:latest&lt;/span&gt;
  &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;telegraf&lt;/span&gt;
  &lt;span class="na"&gt;Volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="s"&gt;./telegraf/telegraf.conf:/etc/telegraf/telegraf.conf:ro&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Can be transformed to let Compose mount the telegraf configuration file via Docker Config as follows:&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="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;telegraf:latest&lt;/span&gt;
  &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;telegraf&lt;/span&gt;
  &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;--config /telegraf_conf&lt;/span&gt;
  &lt;span class="na"&gt;configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;telegraf_conf&lt;/span&gt;

&lt;span class="na"&gt;configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;telegraf_conf&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./telegraf/telegraf.conf&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, the configuration is mounted in the root of the container with the name of the docker config mapped to the file of the configuration file. When using such a configuration it is essential to mention within the &lt;code&gt;command&lt;/code&gt; parameter where should telegraf load the configuration file from i.e. &lt;code&gt;command: –-config /telegraf_conf&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Inference
&lt;/h2&gt;

&lt;p&gt;This guide should provide a baseline for how Engineers can plan to design their Telegraf Configuration file(s) with Docker Compose v2 as a deployment tool. This is not an exhaustive guide by any means, but it explores some new features in Telegraf (Docker Secret Store) and some less familiar Docker Compose v2 specifications that may not be often used in deployments.&lt;/p&gt;

&lt;p&gt;Resources / Links&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/influxdata/telegraf/tree/master/plugins/secretstores"&gt;Telegraf’s Secretstores Plugin implementation on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/influxdata/telegraf/tree/master/plugins/secretstores/docker"&gt;Telegrafs’ Docker Secret Store Plugin Implementation on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://compose-spec.io/"&gt;Compose Specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.docker.com/blog/new-docker-compose-v2-and-v1-deprecation/"&gt;Additonal Information on Docker Secrets with Docker Compose via Environment Variables - Source Docker Blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>docker</category>
      <category>devops</category>
    </item>
    <item>
      <title>How Reproducibility + Documentation in Software goes deep: Practical Scenario</title>
      <dc:creator>Shan Desai</dc:creator>
      <pubDate>Sun, 11 Jun 2023 18:58:53 +0000</pubDate>
      <link>https://dev.to/shandesai/how-reproducibility-documentation-in-software-goes-deep-practical-scenario-803</link>
      <guid>https://dev.to/shandesai/how-reproducibility-documentation-in-software-goes-deep-practical-scenario-803</guid>
      <description>&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;I have been working on a personal project which I recently open-sourced called &lt;a href="https://github.com/shantanoo-desai/komponist"&gt;Komponist&lt;/a&gt;.&lt;br&gt;
In a nutshell, the project is heavily reliant on some of the most widely used tools today in&lt;br&gt;
the software landscape i.e., &lt;strong&gt;Ansible&lt;/strong&gt;, &lt;strong&gt;Docker&lt;/strong&gt;, &lt;strong&gt;Docker Compose&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Everything was &lt;em&gt;smooth-sailing&lt;/em&gt; with a new feature planned when suddenly I started getting a&lt;br&gt;
strange error when bringing my container stack up using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose &lt;span class="nt"&gt;--project-directory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;deploy up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The error was rather mysterious and not much verbose. It screamed something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Error response from daemon: Could not find the file / &lt;span class="k"&gt;in &lt;/span&gt;container &amp;lt;container_id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This seemed rather odd, the same thing that worked a couple of days back now is rendered &lt;br&gt;
buggy! As usual common searches didn't lead me anywhere. What was rather weird was the fact that&lt;br&gt;
I never was mounting any root directory anywhere (&lt;code&gt;/&lt;/code&gt;). The good thing about this error was&lt;br&gt;
I had only two realms to look into, either &lt;strong&gt;Docker Compose v2&lt;/strong&gt; tool or the &lt;strong&gt;Docker Engine&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Hackin' Around
&lt;/h3&gt;

&lt;p&gt;Since this was a sudden bug that I faced with, I suspected initially something going wrong with &lt;br&gt;
the Compose Files (&lt;code&gt;docker-compose.yml&lt;/code&gt;) file which my tool generates. However, this felt rather&lt;br&gt;
counter-intuitive because the tool itself also takes up the initiative to validate all my Compose Files.&lt;br&gt;
Had there been any error, the validation logic would have failed and hence would not have generated me&lt;br&gt;
a production-ready file in the first place.&lt;/p&gt;

&lt;p&gt;Through some previous recollections, I was conscious about User ID within the container causing problems&lt;br&gt;
especially when trying to mount secrets into Docker Containers. In essence, my tool mounts Secrets (credentials)&lt;br&gt;
into the container via Docker Compose's specification which copies the values under the &lt;code&gt;/run/secrets&lt;/code&gt; directory&lt;br&gt;
within a container.&lt;/p&gt;

&lt;p&gt;This feature, although great had some small caveats. As security best-practice most containers have a user within&lt;br&gt;
them, which does &lt;em&gt;NOT&lt;/em&gt; have enough privileges to actually mount the secrets into the &lt;code&gt;/run/secrets&lt;/code&gt; directory.&lt;br&gt;
The only exception being container with &lt;code&gt;root&lt;/code&gt; user (which is generally not the best idea in the first place).&lt;/p&gt;

&lt;p&gt;A common fix is to add &lt;code&gt;user&lt;/code&gt; parameter to each service with the current Host's User ID, e.g., &lt;code&gt;1000&lt;/code&gt; which &lt;br&gt;
will run the container as the same user ID as that of the host's user. This was already part of the current system&lt;br&gt;
in my project.&lt;/p&gt;

&lt;p&gt;I started hacking around thinking this particular value might be the main culprit so I tried adding&lt;br&gt;
various combinations like &lt;code&gt;user: "1000:1000"&lt;/code&gt; or &lt;code&gt;user: "&amp;lt;my_host-machine_name&amp;gt;"&lt;/code&gt; but it did not work.&lt;/p&gt;

&lt;p&gt;A last dumpster dive into some GitHub Issues on the &lt;a href="https://github.com/moby/moby"&gt;Moby Project&lt;/a&gt; which is essentially the Docker Engine&lt;br&gt;
landed me onto &lt;a href="https://github.com/moby/moby/issues/34142"&gt;Issue 34142 for Moby Project&lt;/a&gt; which is open since &lt;strong&gt;2017&lt;/strong&gt; (yes you read that right!).&lt;/p&gt;

&lt;p&gt;At wit's end, I thought if the user on my host has stopped working, I might as well give the user within the image &lt;br&gt;
a shot i.e., if I have a Grafana container, upon running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; grafana/grafana-oss:latest &lt;span class="nb"&gt;whoami
&lt;/span&gt;grafana
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;so updating my &lt;code&gt;user&lt;/code&gt; parameter to &lt;code&gt;user: "grafana"&lt;/code&gt; suddenly brought got everything working!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Temporary fix for the Project! Hurrah!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Reporting the Behaviour
&lt;/h2&gt;

&lt;p&gt;I generally am very thorough in reporting bugs / issues to Open-Source Projects. Essentially I start&lt;br&gt;
with mentioning my Host, all the software versions that are affected (here, Docker Engine / Docker Compose v2)&lt;br&gt;
and provide a Minimum Example that can be reproduced by anyone. Since the fix was pertaining to Docker Compose v2's&lt;br&gt;
&lt;code&gt;user&lt;/code&gt; property I reported a bug on &lt;a href="https://github.com/docker/compose/issues/10663"&gt;Github's docker/compose repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The Maintainers had a look into it, but then came the nightmare answer of:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Tried to reproduce it, I got something else !&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Dread of "It works on my machine, can't reproduce it"
&lt;/h3&gt;

&lt;p&gt;After a few back and forth with the maintainer and even trying out certain examples, it felt like I had messed up&lt;br&gt;
my Docker Engine with some configuration. It wasn't the case, since my configuration file was the least bit &lt;br&gt;
complicated and it was close to running an &lt;em&gt;out-of-the-box&lt;/em&gt; Docker Engine.&lt;/p&gt;

&lt;p&gt;It seemed like this was going to be one of those GitHub Issues that would be left open and gets lost in the &lt;br&gt;
Oblivion of Open-Source Projects. Everything seemed hay-wired.&lt;/p&gt;

&lt;h3&gt;
  
  
  One Last Effort!
&lt;/h3&gt;

&lt;p&gt;Whilst the back and forth between my Bug report and the Maintainer's non-reproducible errors started to reduce,&lt;br&gt;
I reverted back to the an older Docker Engine Version to see if a sudden update started to cause this error on my end.&lt;/p&gt;

&lt;p&gt;For Context, the Issue was about using &lt;strong&gt;Docker Engine v24.x&lt;/strong&gt; with &lt;strong&gt;Docker Compose v2.18.1&lt;/strong&gt;. I downgraded the &lt;br&gt;
Docker Engine to the last &lt;strong&gt;v23.x.x&lt;/strong&gt; and would you know it! Things started to work again. &lt;/p&gt;

&lt;p&gt;I was able to pinpoint out that things were going wrong, since the &lt;em&gt;leap of faith&lt;/em&gt; from Docker Engine &lt;strong&gt;v23&lt;/strong&gt; to &lt;strong&gt;v24&lt;/strong&gt;.&lt;br&gt;
I mentioned this difference in the &lt;a href="https://github.com/docker/compose/issues/10663#issuecomment-1580624862"&gt;Issue's Thread&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So I was convinced that something is breaking between the Docker Engine versions but I wasn't sure whether there was &lt;br&gt;
some discrepancy in Docker Compose v2 too.&lt;/p&gt;

&lt;p&gt;This led me to a previous Compatibility Anaylyses that I had done about a year back called &lt;br&gt;
&lt;a href="https://shantanoo-desai.github.io/posts/technology/compose-incompatibility/"&gt;Exploring the Marshland: Docker Compose Versions&lt;/a&gt;&lt;br&gt;
where I was able to isolate different versions of the software to determine which versions were working with what&lt;br&gt;
specific parameters. The tool I had used was &lt;strong&gt;Vagrant&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Isolate, Compare, Determine!
&lt;/h2&gt;

&lt;p&gt;I took the initiative of creating two &lt;strong&gt;Isolated Vagrant Boxes&lt;/strong&gt; with the following software:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Vagrant Box&lt;/th&gt;
&lt;th&gt;Docker Engine Version&lt;/th&gt;
&lt;th&gt;Docker Compose Version(s)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Box 1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v23.0.6&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;v2.18.1&lt;/code&gt;&lt;br&gt; &lt;code&gt;v2.17.3&lt;/code&gt;&lt;br&gt; &lt;code&gt;v2.16.0&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Box 2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v24.0.2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;v2.18.1&lt;/code&gt;&lt;br&gt; &lt;code&gt;v2.17.3&lt;/code&gt;&lt;br&gt; &lt;code&gt;v2.16.0&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Documenting the Discrepancy!
&lt;/h3&gt;

&lt;p&gt;I decided that I would rather add GIFs of the reproduction of the errors as opposed to adding a lot of&lt;br&gt;
verbosity to an already complicated problem and create a repo that would reduce the time for the Bug reproduction.&lt;br&gt;
This does come with an assumption that the Maintainer would also have &lt;strong&gt;Vagrant&lt;/strong&gt; or something similar to test at their&lt;br&gt;
end.&lt;/p&gt;

&lt;p&gt;I was able to reproduce the same bugs in a rather isolated environment using Vagrant disproving my theory that I had &lt;br&gt;
messed my Docker Engine up and perhaps the Maintainer should try the stuff out again at their end.&lt;/p&gt;

&lt;p&gt;The Repo can be Found on &lt;a href="https://github.com/shantanoo-desai/docker-engine-secrets-error"&gt;GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Inference
&lt;/h2&gt;

&lt;p&gt;Upon providing an environment to reproduce this error as well as some GIF documentation, it turned out that this was&lt;br&gt;
actually an error even the Maintainer can reproduce. This error is related to the Docker Engine v24 and not Docker Compose v2&lt;br&gt;
and it is now being tracked with fixes already on the way.&lt;br&gt;
See &lt;a href="https://github.com/moby/moby/issues/45719"&gt;Moby/Moby Issue #45719&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This might look like a lot of work, which in hindsight might be, but it is worth being comfortable with tools where a decent&lt;br&gt;
amount of reproducibility via Isolation can provide a strong foundation to look into software a bit more deeper!&lt;/p&gt;

&lt;p&gt;Vagrant is meant to be Developer-Friendly tool which indeed helped me overcome the dreadful situation of &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It worked on my machine, why isn't it working on yours?!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It also helped collaborate on a problem which stemmed not in Docker Compose v2 but went all the way down to Docker Engine&lt;br&gt;
itself. The maintainer was able to produce the same error with &lt;a href="https://multipass.run/"&gt;Canonical's multipass&lt;/a&gt; which also&lt;br&gt;
reached the same goal through a different route.&lt;/p&gt;

&lt;p&gt;So in a nutshell, a few hours of going in the deep end of error reproducibility may feel like time wasted but it might&lt;br&gt;
just solve a problem that is long-lasting (till something new pops up!)&lt;/p&gt;

</description>
      <category>docker</category>
      <category>vagrant</category>
      <category>devops</category>
    </item>
    <item>
      <title>Create a minimal OS using Docker Containers and Hashicorp Packer</title>
      <dc:creator>Shan Desai</dc:creator>
      <pubDate>Sun, 18 Sep 2022 11:02:55 +0000</pubDate>
      <link>https://dev.to/shandesai/create-a-minimalist-os-using-docker-containers-and-hashicorp-packer-856</link>
      <guid>https://dev.to/shandesai/create-a-minimalist-os-using-docker-containers-and-hashicorp-packer-856</guid>
      <description>&lt;h2&gt;
  
  
  Why do this?
&lt;/h2&gt;

&lt;p&gt;I was mightly impressed by &lt;a href="https://iximiuz.com/en/posts/from-docker-container-to-bootable-linux-disk-image/"&gt;Ivan Velichko's Blogpost&lt;/a&gt; on using Docker Containers&lt;br&gt;
to create bootable Disk images. He even has a &lt;a href="https://github.com/iximiuz/docker-to-linux"&gt;GitHub Repository&lt;/a&gt; that still gets a lot of&lt;br&gt;
attention from developers who have tried and tested things out.&lt;/p&gt;

&lt;p&gt;I am already using &lt;a href="https://packer.io"&gt;Hashicorp Packer&lt;/a&gt; at work and for personal projects and I wanted to test&lt;br&gt;
this idea out by wrapping it a single Packer Template file. This reduces the level of maintaining&lt;br&gt;
a lot of small scripts, Dockerfiles and configurations and the user can simply trigger a couple of &lt;br&gt;
commands to get a minimalist OS at the end of the process.&lt;/p&gt;

&lt;p&gt;Also just sheer curiousity from side. Why not try things out!&lt;/p&gt;
&lt;h3&gt;
  
  
  What else is there in the market?
&lt;/h3&gt;

&lt;p&gt;Don't get me wrong there are some projects already out there doing things similarly, and a lot of products&lt;br&gt;
leverage these tools to ship out custom, minimalist OS images for Edge Computing Devices / Cloud Platforms.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.lfedge.org/projects/eve/"&gt;LF-Edge EVE project&lt;/a&gt; leverages &lt;a href="https://github.com/linuxkit/linuxkit"&gt;Linuxkit&lt;/a&gt; to create custom OSs for Edge Devices which in turn leverages
Containers as Lego Blocks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Albeit these projects do much more than just create a Lego Tower of Docker Containers to create a bootable image,&lt;br&gt;
but if it peeks your curiosity you should check them out.&lt;/p&gt;
&lt;h2&gt;
  
  
  Presenting: PockerISO
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/shantanoo-desai/PockerISO"&gt;PockerISO&lt;/a&gt; is just Packer + Docker to create bootable images. The difference here is that Packer does the&lt;br&gt;
heavy-lifting of doing the following things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pulling the base images&lt;/li&gt;
&lt;li&gt;bringing them up&lt;/li&gt;
&lt;li&gt;executing the respective shell scripts within the container&lt;/li&gt;
&lt;li&gt;saving/exporting the current filesystem state into a tarball &lt;/li&gt;
&lt;li&gt;creating an isolated container to execute the steps to create the bootable image without disturbing the host machine fs&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Packer is the Maestro and Docker is the Orchestra that plays the symphony, and at the end the end-user cheers with&lt;br&gt;
a nice minimalist OS image&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  How does PockerISO work?
&lt;/h3&gt;
&lt;h3&gt;
  
  
  Filesystem Generation
&lt;/h3&gt;

&lt;p&gt;I strongly recommend reading Ivan Velichko's blogpost mentioned previously to get some greate understanding on what needs&lt;br&gt;
to be done. He does some insanely cool visualizations too!&lt;/p&gt;

&lt;p&gt;In a nutshell, containers &lt;strong&gt;DO NOT&lt;/strong&gt; have a kernel (they leverage the kernel of the host machine), and they &lt;strong&gt;DO NOT&lt;/strong&gt; have&lt;br&gt;
system manager, containers always run with Process ID 1.&lt;/p&gt;

&lt;p&gt;So in order to make an operating system, we will install these two things in the container base image to atleast create a&lt;br&gt;
filesystem tarball which we can use to create an image.&lt;/p&gt;

&lt;p&gt;Once the container installs the kernel, generates the respective &lt;code&gt;initramfs&lt;/code&gt; for us and installs &lt;code&gt;systemd&lt;/code&gt; via APTITUDE &lt;br&gt;
package manager, we tell Packer to finish of this process by creating a tarball of the container's filesystem.&lt;/p&gt;
&lt;h4&gt;
  
  
  Bootable Image creation
&lt;/h4&gt;

&lt;p&gt;Once the tarball is created, we extract it on the host for the simple reason that extracting the tarball within a docker &lt;br&gt;
container generally leads to a lot of Failures when done through via Packer.&lt;/p&gt;

&lt;p&gt;We then spin up a container of the same base image copy the necessary files and folders into it. The main thing to note here&lt;br&gt;
is this container is brought up with &lt;code&gt;privileged&lt;/code&gt; mode. Remember we will still need the host device to use Operating System&lt;br&gt;
Loop devices, to mount filesystems and create the final base image.&lt;/p&gt;

&lt;p&gt;Within this phase we do the following via a dedicated shell script:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create an empty image file using &lt;code&gt;dd&lt;/code&gt; of approximately 1GB&lt;/li&gt;
&lt;li&gt;Make Partitions for the disk image&lt;/li&gt;
&lt;li&gt;Create a filesystem on the image with &lt;code&gt;ext4&lt;/code&gt; format&lt;/li&gt;
&lt;li&gt;Copy our filesystem with the kernel, systemd and initramfs&lt;/li&gt;
&lt;li&gt;Install a bootloader and create a boot partition on the image&lt;/li&gt;
&lt;li&gt;Mount / Unmount the dedicated image&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The final result will be an &lt;code&gt;.img&lt;/code&gt; as well as &lt;code&gt;.qcow2&lt;/code&gt; image that can be tinkered with using QEMU.&lt;/p&gt;

&lt;p&gt;As a use you only need to execute a simple &lt;code&gt;make&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;make ubuntu # or `make debian`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and at the end of the magical automation rainbow you will have a minimalist OS you can play with.&lt;/p&gt;

&lt;h2&gt;
  
  
  What base images does PockerISO offer?
&lt;/h2&gt;

&lt;p&gt;At this point the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ubuntu&lt;/strong&gt;: 20.04, 22.04&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debian&lt;/strong&gt;: &lt;strong&gt;bullseye&lt;/strong&gt;, &lt;strong&gt;bookworm&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Potential Updates
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;I am planning to add Alpine Images with it's own Packer Template&lt;/li&gt;
&lt;li&gt;I wish to make this repository compatible also for &lt;code&gt;linux/arm64&lt;/code&gt; images since Docker lets you cross-compile images via BuildKit.
The only caveat is that for devices like the Raspberry Pi, the bootloader logic might be a tad bit tedious to figure out.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you would like to give some feedback, criticisms, suggestions on improving the project or this write-up leave me a message on &lt;br&gt;
LinkedIn and I will happily revert back.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>packer</category>
      <category>opensource</category>
      <category>docker</category>
    </item>
    <item>
      <title>Customized Ubuntu Images using Packer + QEMU + Cloud-Init &amp; UEFI bootloading</title>
      <dc:creator>Shan Desai</dc:creator>
      <pubDate>Sun, 21 Aug 2022 15:49:00 +0000</pubDate>
      <link>https://dev.to/shandesai/customized-ubuntu-images-using-packer-qemu-cloud-init-uefi-bootloading-3255</link>
      <guid>https://dev.to/shandesai/customized-ubuntu-images-using-packer-qemu-cloud-init-uefi-bootloading-3255</guid>
      <description>&lt;h2&gt;
  
  
  Custom Ubuntu Images
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;NOTE: This post requires more than intermediate knowledge of the tools being used.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In current times, you might want to spin up a customized, well-known Distribution like Ubuntu, Debian, CentOS etc.&lt;br&gt;
without having to write large Shell Scripts. This post sheds light on how to create images using tools like&lt;br&gt;
&lt;strong&gt;Hashicorp's Packer&lt;/strong&gt; and &lt;strong&gt;QEMU (Quick Emulator)&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Cloud-Init
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://cloud-init.io"&gt;Cloud-Init&lt;/a&gt; is a &lt;em&gt;standardized&lt;/em&gt; way to configure your Images without having to write shell scripts. It is a set of YAML&lt;br&gt;
files that tells the image what needs to be done on the first-boot of the OS. We will use Cloud-Init to create the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create 2 Users (&lt;code&gt;admin&lt;/code&gt; and &lt;code&gt;user1&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Set the Bootloader Sequence when trying to boot the Image using &lt;strong&gt;UEFI&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Packer
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://packer.io"&gt;Hashicorp Packer&lt;/a&gt; provides a nice wrapper / abstraction over the QEMU in order to boot the image and use it to set it up on first-boot.&lt;br&gt;
Instead of writing really long commands in order to boot up the image using QEMU, Packer provided a nice Configuration Template in a more&lt;br&gt;
readable fashion.&lt;/p&gt;
&lt;h2&gt;
  
  
  QEMU
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.qemu.org"&gt;QEMU&lt;/a&gt; is one of the most renowned emulator. We will use it to actually boot the ISO image, which Packer will download for us and use it &lt;br&gt;
customize our Ubuntu Image. We will use the &lt;code&gt;X86_64&lt;/code&gt; architecture.&lt;/p&gt;
&lt;h2&gt;
  
  
  Ubuntu Live-Server
&lt;/h2&gt;

&lt;p&gt;You can use either the Cloud Images or Live-Server from Ubuntu depending on your use. We will be using Ubuntu's Live-Server.&lt;/p&gt;
&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;NOTE: The configurations used here will work for Ubuntu 20.04 LTS (Focal Fossa) as well as Ubuntu 22.04 (Jammy Jellyfish)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Pre-Requisites
&lt;/h3&gt;

&lt;p&gt;Make sure to install:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;qemu&lt;/code&gt; (Optionally &lt;code&gt;kvm&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;packer&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Cloud-Init: &lt;code&gt;user-data&lt;/code&gt; File
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;user-data&lt;/code&gt; file is where we will be add configuration that is needed for first boot. As a clarification, Ubuntu Live Server uses a tool&lt;br&gt;
called &lt;strong&gt;Autoinstall / Subiquity&lt;/strong&gt; Installer wherein Cloud-Init configuration is a subset.&lt;/p&gt;

&lt;p&gt;As previously mentioned, we will use this file to setup:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create Two Users: &lt;code&gt;admin&lt;/code&gt; and &lt;code&gt;user1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Setup the bootloader logic in order to quickly boot the ISO after the first-boot
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;#cloud-config&lt;/span&gt;
&lt;span class="na"&gt;autoinstall&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;en_US&lt;/span&gt;
  &lt;span class="na"&gt;keyboard&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;us&lt;/span&gt;
  &lt;span class="na"&gt;ssh&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;install-server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;allow-pw&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;qemu-guest-agent&lt;/span&gt;
  &lt;span class="na"&gt;late-commands&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;sudo&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;apt&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;update&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;sudo&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;apt&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;install&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-y&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;efibootmgr"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sudo&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;efibootmgr"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sudo&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;efibootmgr&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-o&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0007,0001,0000,0002,0003,0004,0005,0006"&lt;/span&gt;
  &lt;span class="na"&gt;user-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;preserve_hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;packerubuntu&lt;/span&gt;
    &lt;span class="na"&gt;package_upgrade&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;timezone&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Europe/Berlin&lt;/span&gt;
    &lt;span class="na"&gt;chpasswd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;expire&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;list&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;user1:packerubuntu&lt;/span&gt;
    &lt;span class="na"&gt;users&lt;/span&gt;&lt;span class="pi"&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;admin&lt;/span&gt;
        &lt;span class="na"&gt;passwd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$6$xyz$74AlwKA3Z5n2L6ujMzm/zQXHCluA4SRc2mBfO2/O5uUc2yM2n2tnbBMi/IVRLJuKwfjrLZjAT7agVfiK7arSy/&lt;/span&gt;
        &lt;span class="na"&gt;groups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;adm&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;cdrom&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;dip&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;plugdev&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;lxd&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;sudo&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;lock-passwd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
        &lt;span class="na"&gt;sudo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ALL=(ALL) NOPASSWD:ALL&lt;/span&gt;
        &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/bin/bash&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;user1&lt;/span&gt;
        &lt;span class="na"&gt;plain-txt-passwd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;packerubuntu&lt;/span&gt;
        &lt;span class="na"&gt;lock-passwd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
        &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/bin/bash&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The above mentioned file does the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;the &lt;code&gt;autoinstall&lt;/code&gt; is Ubuntu's AutoInstall / Subiquity configuration section which will set:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keyboard locale to &lt;code&gt;en_US&lt;/code&gt; and layout to US&lt;/li&gt;
&lt;li&gt;Install SSH Server and allow Password login (will be used by Packer)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;packages&lt;/code&gt; will install some necessary packages on first-boot. Here we install &lt;code&gt;qemu-guest-agent&lt;/code&gt; to help out
with login&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;late-commands&lt;/code&gt; will be triggered at the end of the installation. Here we install the Bootloader Manager (&lt;code&gt;efibootmgr&lt;/code&gt;).
We also define the sequence that tells the Boot Manager how it should setup the boot sequence. This will tell the manager
where the OS is and when should it be loaded&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;user-data&lt;/code&gt; is the actual section where the Cloud-Init configuration takes place.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The cloud-init configuration above should do the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;change the hostname to &lt;code&gt;packerubuntu&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;set the timezone &lt;code&gt;Europe/Berlin&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Force change the password for the user called &lt;code&gt;user1&lt;/code&gt; through &lt;code&gt;chpasswd&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Describe which users need to be created:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;admin&lt;/code&gt; should be create with the password &lt;code&gt;packerubuntu&lt;/code&gt; (the encrypted password is created using &lt;code&gt;openssl passwd -6 -salt xyz&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;admin&lt;/code&gt; should be granted sudo access without requirements for password&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;user1&lt;/code&gt; should be created with &lt;code&gt;packerubuntu&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Packer File
&lt;/h3&gt;

&lt;p&gt;Packer configuration will set all the necessary values to download the ISO Image from Ubuntu Artifacts Repository, give the boot command&lt;br&gt;
options when the ISO is first booted, tell Ubuntu that it will do an automatic installation rather than anticipating the user to intervene.&lt;/p&gt;

&lt;p&gt;I am using the Hashicorp Language to define my Packer template file but one can also JSON to setup the configuration. The file is called&lt;br&gt;
&lt;code&gt;ubuntu.pkr.hcl&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;
&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"vm_template_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ubuntu-22.04"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"ubuntu_iso_file"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ubuntu-22.04.1-live-server-amd64.iso"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;"qemu"&lt;/span&gt; &lt;span class="s2"&gt;"custom_image"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="c1"&gt;# Boot Commands when Loading the ISO file with OVMF.fd file (Tianocore) / GrubV2&lt;/span&gt;
  &lt;span class="nx"&gt;boot_command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"&amp;lt;spacebar&amp;gt;&amp;lt;wait&amp;gt;&amp;lt;spacebar&amp;gt;&amp;lt;wait&amp;gt;&amp;lt;spacebar&amp;gt;&amp;lt;wait&amp;gt;&amp;lt;spacebar&amp;gt;&amp;lt;wait&amp;gt;&amp;lt;spacebar&amp;gt;&amp;lt;wait&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"e&amp;lt;wait&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"&amp;lt;down&amp;gt;&amp;lt;down&amp;gt;&amp;lt;down&amp;gt;&amp;lt;end&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;" autoinstall ds=nocloud-net&lt;/span&gt;&lt;span class="err"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;;s=http://{{ .HTTPIP }}:{{ .HTTPPort }}/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"&amp;lt;f10&amp;gt;"&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;boot_wait&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"5s"&lt;/span&gt;

  &lt;span class="nx"&gt;http_directory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http"&lt;/span&gt;
  &lt;span class="nx"&gt;iso_url&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://releases.ubuntu.com/22.04.1/${var.ubuntu_iso_file}"&lt;/span&gt;
  &lt;span class="nx"&gt;iso_checksum&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"file://https://releases.ubuntu.com/22.04.1/SHA256SUMS"&lt;/span&gt;
  &lt;span class="nx"&gt;memory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;

  &lt;span class="nx"&gt;ssh_password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"packerubuntu"&lt;/span&gt;
  &lt;span class="nx"&gt;ssh_username&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"admin"&lt;/span&gt;
  &lt;span class="nx"&gt;ssh_timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"20m"&lt;/span&gt;
  &lt;span class="nx"&gt;shutdown_command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"echo 'packerubuntu' | sudo -S shutdown -P now"&lt;/span&gt;

  &lt;span class="nx"&gt;headless&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="c1"&gt;# to see the process, In CI systems set to true&lt;/span&gt;
  &lt;span class="nx"&gt;accelerator&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"kvm"&lt;/span&gt; &lt;span class="c1"&gt;# set to none if no kvm installed&lt;/span&gt;
  &lt;span class="nx"&gt;format&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"qcow2"&lt;/span&gt;
  &lt;span class="nx"&gt;disk_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"30G"&lt;/span&gt;
  &lt;span class="nx"&gt;cpus&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;

  &lt;span class="nx"&gt;qemuargs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="c1"&gt;# Depending on underlying machine the file may have different location&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-bios"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"/usr/share/OVMF/OVMF_CODE.fd"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt; 
  &lt;span class="nx"&gt;vm_name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.vm_template_name}"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;build&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;sources&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"source.qemu.custom_image"&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;provisioner&lt;/span&gt; &lt;span class="s2"&gt;"shell"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;inline&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"while [ ! -f /var/lib/cloud/instance/boot-finished ]; do echo 'Waiting for Cloud-Init...'; sleep 1; done"&lt;/span&gt; &lt;span class="p"&gt;]&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;The Template file tells Packer where to find and download the ISO file. The &lt;code&gt;boot_command&lt;/code&gt; is quite important because it tells &lt;br&gt;
Packer how to navigate through the initial Boot Loaders Interface in order to enter the Grub Settings where we mention:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  autoinstall ds=cloud-net\\;s=http://{{ .HTTPIP}}:{{ .HTTPPort }}/ 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will tell ubuntu to install the Live server without manual intervention and obtain the cloud-init configuration files&lt;br&gt;
from an HTTP File Server (setup by Packer itself).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;NOTE: please check beforehand where the dedicated OVMF.fd file is located on your build machine. In Majaro Linux it is located&lt;br&gt;
      at &lt;code&gt;/usr/share/OVMF/OVMF_CODE.fd&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;code&gt;headless = false&lt;/code&gt; is very useful because Packer will open a VNC Viewer window that will completely show all the necessary&lt;br&gt;
Boot loader UI + Setups + Logs. If using this file in a CI Pipeline, please set the value to &lt;code&gt;true&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;build&lt;/code&gt; section of the template file will just check whether the Cloud-Init Steps have been finished or not.&lt;/p&gt;

&lt;p&gt;Run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;packer build &lt;span class="nt"&gt;-force&lt;/span&gt; ubuntu.pkr.hcl
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will do all the magic for you! At the end you will have &lt;code&gt;qcow2&lt;/code&gt; image which you can use to load it on your bare-metal machine&lt;br&gt;
or use &lt;code&gt;qemu&lt;/code&gt; commands to simply boot it up and test it.&lt;/p&gt;

&lt;p&gt;Voilà ! You now have a custom Ubuntu Image which you can load on your devices, servers etc. and do not have to painstakingly configure&lt;br&gt;
anything by hand!&lt;/p&gt;

&lt;p&gt;Don't let this stop you here, Packer let's you provision your Images with all necessary software packages using your favorite tools like&lt;br&gt;
Ansible, Chef, Puppet. This implies you can even fine tune your images further to make your images exactly the way you want it to be.&lt;/p&gt;

&lt;h2&gt;
  
  
  Repository
&lt;/h2&gt;

&lt;p&gt;The code for this post can be found on &lt;a href="https://github.com/shantanoo-desai/packer-ubuntu-server-uefi"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Get in touch via LinkedIn, Email if you have queries, suggestions or criticism about this post! &lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.aerialls.eu/posts/ubuntu-server-2204-image-packer-subiquity-for-proxmox/"&gt;Julien Brochet's Blog Post on Using Packer + Proxmox for Ubuntu 22.04&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/dogukancagatay/qemu-vm-template-packer"&gt;Dogukan Cagatay's QEMU VM Template Packer Repo&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.puppeteers.net/blog/building-ubuntu-20-04-qemu-images-with-packer/"&gt;Pupeteers.net Blog on Ubuntu 20.04 qemu images with Packer&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>packer</category>
      <category>ubuntu</category>
      <category>qemu</category>
    </item>
    <item>
      <title>Exploring the Marshland of Docker Compose Versions with single quotes, special characters and environment variables</title>
      <dc:creator>Shan Desai</dc:creator>
      <pubDate>Fri, 15 Jul 2022 16:06:41 +0000</pubDate>
      <link>https://dev.to/shandesai/exploring-the-marshland-of-docker-compose-versions-with-single-quotes-special-characters-and-environment-variables-236f</link>
      <guid>https://dev.to/shandesai/exploring-the-marshland-of-docker-compose-versions-with-single-quotes-special-characters-and-environment-variables-236f</guid>
      <description>&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;Since &lt;a href="https://www.docker.com/blog/announcing-compose-v2-general-availability/"&gt;Docker's Compose V2 General Availability&lt;/a&gt; announcement, a lot of things have changed which might lead to &lt;br&gt;
Incompatibility in your currently running application stacks.&lt;/p&gt;
&lt;h3&gt;
  
  
  Compose V2: what changes?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;instead of &lt;code&gt;docker-compose&lt;/code&gt; the syntax will be &lt;code&gt;docker compose&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docker-compose&lt;/code&gt; (v1) was written in Python, &lt;code&gt;docker compose&lt;/code&gt; (v2) is in Go&lt;/li&gt;
&lt;li&gt;Compose V1 is official EOL (End Of Life) aka. Deprecated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are lot of fine things in Compose V2 but this post isn't about it. This write-up takes you through the murky &lt;br&gt;
marshland between Compose V1 and Compose V2 and how things might start you break when you decide to jump to the &lt;br&gt;
&lt;em&gt;cool V2&lt;/em&gt; vers without doing some thorough checks in your stack application&lt;/p&gt;
&lt;h2&gt;
  
  
  Swiss Knife for the Exploration
&lt;/h2&gt;

&lt;p&gt;Tools you will need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hashicorp Vagrant&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ansible&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Different versions of Docker Compose (v1 as well as v2)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Vagrant
&lt;/h3&gt;

&lt;p&gt;Instead of setting up a pain-staking Virtual Machine, we will leverage a bit simplified and inexpensive Vagrant Machine&lt;br&gt;
with &lt;strong&gt;Ubuntu 20.04 (focal amd64) base image&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Within this image we will install the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docker Engine &lt;code&gt;20.10.17&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Docker Compose v1: &lt;code&gt;v1.25.4 build 8d51620a&lt;/code&gt; and &lt;code&gt;v1.29.2 build 5b3cea4c&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Docker Compose v2: &lt;code&gt;2.6.0&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Ansible
&lt;/h3&gt;

&lt;p&gt;I have Ansible installed on my host machine&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ansible &lt;span class="nt"&gt;--version&lt;/span&gt;
ansible &lt;span class="o"&gt;[&lt;/span&gt;core 2.13.1]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ansible will install all the spicy Compose versions into the Vagrant Machine for us&lt;/p&gt;

&lt;h2&gt;
  
  
  Why are we doing this?
&lt;/h2&gt;

&lt;p&gt;If you have containers that require passwords at rest e.g. in &lt;code&gt;.env&lt;/code&gt; environment files to be encrypted and with &lt;br&gt;
special characters like the &lt;code&gt;$&lt;/code&gt; in them, we are going for ride which will make you want to bookmark this write-up.&lt;/p&gt;

&lt;p&gt;It turns out, I had a system running Compose v1 (&lt;code&gt;v1.25.4&lt;/code&gt;) and upon upgrading to Compose v2 (&lt;code&gt;2.6.0&lt;/code&gt;) I wasn't able&lt;br&gt;
to login to my Node-RED container which had authentication setup already.&lt;/p&gt;
&lt;h3&gt;
  
  
  Node-RED Container
&lt;/h3&gt;

&lt;p&gt;Node-RED requires that the administrator password must be encrypted in &lt;code&gt;bcrypt&lt;/code&gt; type scheme and the password looks&lt;br&gt;
something like your dog walked over your keyboard.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks fairly okay, but the menace is the &lt;code&gt;$&lt;/code&gt; characters in them.&lt;/p&gt;

&lt;p&gt;Let's dive deep shall we!&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;Vagrantfile&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;I am using a Linux Machine with Virtual Box as a provider&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# -*- mode: ruby -*-
# vi: set ft=ruby :


Vagrant.configure("2") do |config|

  # Use Ubuntu 20.04 as base
  config.vm.box = "ubuntu/focal64"

  config.vm.box_check_update =false

  # Synchronize all the docker-compose related file to `/vagrant` dir in VM
  config.vm.synced_folder ".", "/vagrant"

  config.vm.provider "virtualbox" do |vb|
    vb.memory = "2048"
  end

  # Setup the VM with all the different variants of Docker Compose in them
  config.vm.provision "ansible" do |a|
    a.verbose = "v"
    a.playbook = "./docker-compose-playbook.yml"
  end
end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Ansible Playbook: &lt;code&gt;docker-compose-playbook.yml&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;We will let Ansible install all the required Docker Compose versions for us in the VM through the playbook.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docker Compose v1.25.4 will be available in the VM as CLI &lt;code&gt;docker-compose-125&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Docker Compose v1.29.2 will be available in the VM as CLI &lt;code&gt;docker-compose-129&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Docker Compose v2.6.0 will be available in the VM as CLI &lt;code&gt;docker compose&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;all&lt;/span&gt;
  &lt;span class="na"&gt;become&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
  &lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="pi"&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;install prerequisites&lt;/span&gt;
      &lt;span class="na"&gt;apt&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="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;apt-transport-https&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ca-certificates&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;curl&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gnupg-agent&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;software-properties-common&lt;/span&gt;
        &lt;span class="na"&gt;update_cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&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;add apt-key&lt;/span&gt;
      &lt;span class="na"&gt;apt_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://download.docker.com/linux/ubuntu/gpg&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;add docker repo&lt;/span&gt;
      &lt;span class="na"&gt;apt_repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deb https://download.docker.com/linux/ubuntu focal stable&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;install docker&lt;/span&gt;
      &lt;span class="na"&gt;apt&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="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;docker-ce&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;docker-ce-cli&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;containerd.io&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;docker-compose-plugin&lt;/span&gt;
        &lt;span class="na"&gt;update_cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&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;add userpermissions&lt;/span&gt;
      &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;usermod&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-aG&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;docker&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;vagrant"&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;Install docker-compose v1.29.2 from official github repo&lt;/span&gt;
      &lt;span class="na"&gt;get_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;url &lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/docker/compose/releases/download/1.29.2/docker-compose-Linux-x86_64&lt;/span&gt;
        &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/usr/local/bin/docker-compose-129&lt;/span&gt;
        &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;u+x,g+x'&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;change compose v1.29.2 user permissions&lt;/span&gt;
      &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chmod&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;+x&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;/usr/local/bin/docker-compose-129"&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;Install docker-compose v1.25.4 from official github repo&lt;/span&gt;
      &lt;span class="na"&gt;get_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/docker/compose/releases/download/1.25.4/docker-compose-Linux-x86_64&lt;/span&gt;
        &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/usr/local/bin/docker-compose-125&lt;/span&gt;
        &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;u+x,g+x'&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;change compose v1.25.4 user permissions&lt;/span&gt;
      &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chmod&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;+x&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;/usr/local/bin/docker-compose-125"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Files for reproduction of incompatibility
&lt;/h3&gt;

&lt;p&gt;We wont be spinning to much complex stuff so a simple compose file and the relevant &lt;code&gt;.env.node-red&lt;/code&gt;&lt;br&gt;
file with the encrypted password should suffice&lt;/p&gt;
&lt;h4&gt;
  
  
  &lt;code&gt;docker-compose.yml&lt;/code&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.7'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;node-red&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nodered/node-red:2.2.2&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test-nodered&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.env.node-red&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;.env.node-red&lt;/code&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Encrypting plain-text password with value `password` with bcyrpt
NODERED_ADMIN_PASSWORD=$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Compatibility Checks
&lt;/h2&gt;

&lt;p&gt;Let's bring the Vagrant Machine up using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vagrant up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;this will download the base ubuntu image if you previously don't have it on your host machine,&lt;br&gt;
provision it with all the docker compose versions in it.&lt;/p&gt;

&lt;p&gt;Login into the Machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;vagrant ssh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once into the Machine head to the &lt;code&gt;/vagrant&lt;/code&gt; directory, and just to be safe we check if all the &lt;br&gt;
required compose versions are available or not.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /vagrant

&lt;span class="nv"&gt;$ &lt;/span&gt;docker-compose-125 &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker-compose-129 &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker compose version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Checks: Password not encloses in Single Quotes
&lt;/h3&gt;

&lt;p&gt;As the &lt;code&gt;.env.node-red&lt;/code&gt; file currently shows that my encrypted password is not enclosed in single quotes&lt;/p&gt;

&lt;p&gt;Let's understand what Compose thinks of the value of the environment variable.&lt;/p&gt;

&lt;h4&gt;
  
  
  Compose v1.25.4 (without single quotes)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose-125 config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;produces&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="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;node-red&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test-nodered&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;NODERED_ADMIN_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$$2a$$08$$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nodered/node-red:2.2.2&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.7'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;NO Problem! My dollar characters are escaped by docker-compose. I can login with password &lt;code&gt;password&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Compose v1.29.2 (without single quotes)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose-129 config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;produces&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="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;node-red&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test-nodered&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;NODERED_ADMIN_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$$2a$$08$$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nodered/node-red:2.2.2&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.7'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;NO Problem! My dollar characters are escaped by docker-compose. I can login with password &lt;code&gt;password&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Compose v2 without single quotes)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;produces&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="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node-red-app&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;node-red&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test-nodered&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;NODERED_ADMIN_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;a$$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nodered/node-red:2.2.2&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;null&lt;/span&gt;
&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;default&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;node-red-app_default&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Oh NO! What happened here? Password looks eaten up. The YAML file looks different too!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Checks: Password enclosed in single quotes
&lt;/h3&gt;

&lt;p&gt;Now let's enclose our encrypted password between single quotes, so our &lt;code&gt;.env.node-red&lt;/code&gt; file&lt;br&gt;
should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NODERED_ADMIN_PASSWORD=$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Compose v1.25.4 (with single quotes)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose-125 config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;produces&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="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;node-red&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test-nodered&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;NODERED_ADMIN_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;$$2a$$08$$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.'&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nodered/node-red:2.2.2&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.7'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Oh NO! Why are there more single quotes than expected. This is not going to let me login!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Compose v1.29.2 (with single quotes)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose-129 config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;produces&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="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;node-red&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test-nodered&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;NODERED_ADMIN_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$$2a$$08$$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nodered/node-red:2.2.2&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.7'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;NO problem! here this will work fine as the dollar characters are escaped safely by compose&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Compose v2 (with single quotes)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;produces&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="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node-red-app&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;node-red&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test-nodered&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;NODERED_ADMIN_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$$2a$$08$$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nodered/node-red:2.2.2&lt;/span&gt;
    &lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;null&lt;/span&gt;
&lt;span class="na"&gt;networks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;default&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;node-red-app_default&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;NO Problem! Phew ! This will work just fine ! &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Result: Compose Compatibility Matrix
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Compose Version / Values&lt;/th&gt;
&lt;th&gt;&lt;code&gt;1.25.4&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;1.29.2&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;2.6.0&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;with Single Quotes&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;without Single Quotes&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  What did we learn today, kids?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;No Single Quotes&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docker Compose v1.25.4 was able to resolve special characters without having enclose them in single quotes&lt;/li&gt;
&lt;li&gt;Docker Compose v1.29.2 will also work just fine for values not enclosed in single quotes&lt;/li&gt;
&lt;li&gt;Docker Compose v2 will try go hay-wire (try to resolve the value after the &lt;code&gt;$&lt;/code&gt; sign) if you do not use things in single quotes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;With Single Quotes&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Docker Compose v1.25.4 will &lt;em&gt;literally&lt;/em&gt; take things enclosed in single quotes as part of your environment variable and will&lt;br&gt;
break your Container's Login logic&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Docker Compose v1.29.2 will work well&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Docker Compose v2 will work well&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In a nutshell&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;DON'T JUMP THE GUN, when moving from different V1 to V2 in Compose. Do some throrough research as well as experiments.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Since Compose V1 is now deprecated, when transitioning to Compose V2 check all your environment variable files and if they require&lt;br&gt;
special characters to be either enclosed in single quotes or not. Estimate your downtimes and conduct isolated tests when required.&lt;/p&gt;

&lt;p&gt;Rule of thumb from Compose V2 onwards: &lt;em&gt;if it got some special char in them password, pack em between single quotes&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;but to each their own!&lt;/p&gt;

&lt;h3&gt;
  
  
  Github Gist
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/shantanoo-desai/b5d971c533509fdf978f9e97e584b0d0"&gt;GitHub Gist available publicly&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devops</category>
      <category>docker</category>
      <category>vagrant</category>
      <category>ansible</category>
    </item>
    <item>
      <title>Multi-Image Docker Images: Using COPY with Images directly from registries</title>
      <dc:creator>Shan Desai</dc:creator>
      <pubDate>Fri, 24 Jun 2022 20:10:18 +0000</pubDate>
      <link>https://dev.to/shandesai/multi-image-docker-images-using-copy-with-images-directly-from-registries-50ed</link>
      <guid>https://dev.to/shandesai/multi-image-docker-images-using-copy-with-images-directly-from-registries-50ed</guid>
      <description>&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;I was on the lookout for a solution to a problem in Docker that needed to do the following:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Create a Docker Image for a bunch of compiled ASP.NET &lt;code&gt;.dll&lt;/code&gt; files and try reducing the &lt;br&gt;
container footprint&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sounds simple, right? But it turned out to be a bit more &lt;em&gt;messy&lt;/em&gt; than I imagined. To top it off,&lt;br&gt;
I have &lt;strong&gt;NEVER&lt;/strong&gt; even programmed in .NET so I was feeling like a visually impaired person made to &lt;br&gt;
navigate his way through a dark room with furniture scattered all over!&lt;/p&gt;
&lt;h2&gt;
  
  
  Thousand Leagues under the Container Sea!
&lt;/h2&gt;

&lt;p&gt;Keeping the whole Maritime theme alive with Docker (and Kubernetes), I jumped into the sea of &lt;br&gt;
Docker Hub with millions of containers and found out that Microsoft hosts all the .NET related &lt;br&gt;
container images as &lt;a href="https://hub.docker.com/_/microsoft-dotnet"&gt;.NET by Microsoft registry&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Gentle reminder that I have never even thought of .NET before in my life, so just trying to figure out&lt;br&gt;
whether I need an one or more of the following images:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SDK&lt;/li&gt;
&lt;li&gt;ASP.NET Core Runtime&lt;/li&gt;
&lt;li&gt;.NET Runtime&lt;/li&gt;
&lt;li&gt;.NET Runtime Dependencies&lt;/li&gt;
&lt;li&gt;.NET Monitor Tool&lt;/li&gt;
&lt;li&gt;.NET Samples&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;was a side-mission in its own right.&lt;/p&gt;
&lt;h3&gt;
  
  
  Side-Mission
&lt;/h3&gt;

&lt;p&gt;A quick deep-dive into &lt;a href="https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/docker/building-net-docker-images?view=aspnetcore-6.0"&gt;Microsoft's Docker .NET Docs&lt;/a&gt; made me think I only need the following docker image: &lt;br&gt;
&lt;code&gt;mcr.microsoft.com/dotnet/aspnet:6.0&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;So packing everything in a simple &lt;code&gt;Dockerfile&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; mcr.microsoft.com/dotnet/aspnet:6.0-focal&lt;/span&gt;


&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /DotNetVoyage&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./appsettings.json /DotNetVoyage/Server-Files/&lt;/span&gt;

&lt;span class="c"&gt;# Copy every '.dll' file I was handed &lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./* /DotNetVoyage/Server-Files/&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; [ "dotnet", "Server-Files/Server.dll" ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Building it locally and accessing the App initially on the dedicated ports seemed to work just fine and I thought &lt;br&gt;
I was getting off work a bit early today. I jinxed myself.&lt;/p&gt;

&lt;p&gt;Upon some API testing of the image I started seems some logs that were strange to my &lt;em&gt;non .NETian brain&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The specified framework can be found at:
It was not possible to find any compatible framework version
The framework 'Microsoft.AspNetCore.App', version '5.0.0' (x64) was not found.
 - The following frameworks were found:
    6.0.6 at [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
You can resolve the problem by installing the specified framework and/or SDK.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I was head scratching as to how I can make my runtime which is &lt;code&gt;6.0.0&lt;/code&gt; potentially have an SDK version that must &lt;br&gt;
have &lt;code&gt;5.0.0&lt;/code&gt; SDK on it. It got worse, I found out I needed the now &lt;em&gt;defunct and not supported&lt;/em&gt; &lt;code&gt;2.1&lt;/code&gt; version of the SDK&lt;br&gt;
too! &lt;/p&gt;
&lt;h3&gt;
  
  
  Build everything from scratch?
&lt;/h3&gt;

&lt;p&gt;Unaware if there was a way to introduce the different SDKs into the already slimmed container image, I had lost hope &lt;br&gt;
and decided to build an image with maybe &lt;code&gt;debian&lt;/code&gt; or &lt;code&gt;ubuntu&lt;/code&gt; or &lt;code&gt;alpine&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;However, upon asking the Slack Community and rummaging through some StackExchange queries, I traced out a plan that was&lt;br&gt;
pretty familiar to me: &lt;strong&gt;Using Multi-Stage Docker Builds&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Charting towards a Destination
&lt;/h2&gt;

&lt;p&gt;I use Multi-Stage Docker builds almost everyday for work and so I decided to design my container image as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stage 1: pull SDK 2.1
  1.1: find out which directory do the SDK Files persist

Stage 2: pull SDK 5.1
  2.1: find out which directory do the SDK Files persist

Stage Prod: Pull the 6.0.0 Runtime
  Prod.1: copy all the SDK Files (from Stage 1 and Stage 2) to the same directory here in Prod Stage
  Prod.2: copy all the DLL files from host and run the dedicated DLL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Where art thou SDKs?
&lt;/h3&gt;

&lt;p&gt;A quick pull:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker pull mcr.microsoft.com/dotnet/sdk:2.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and check within the container:&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;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;sdk21-check mcr.microsoft.com/dotnet/sdk:2.1 dotnet &lt;span class="nt"&gt;--list-sdks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;resulted in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;2.1.818 &lt;span class="o"&gt;[&lt;/span&gt;/usr/share/dotnet/sdk]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gotcha! the directory for the SDKs is &lt;code&gt;/usr/share/dotnet/&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-Image Image
&lt;/h3&gt;

&lt;p&gt;what I mean by this is whether Docker is smart enough to directly pull an image either using &lt;code&gt;COPY&lt;/code&gt; or &lt;code&gt;ADD&lt;/code&gt;&lt;br&gt;
instructions without me having to create &lt;code&gt;Stage 1&lt;/code&gt; and &lt;code&gt;Stage 2&lt;/code&gt; using syntax such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;mcr.microsoft.com/dotnet/sdk:2.1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base21&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;mcr.microsoft.com/dotnet/sdk:5.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base50&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It turns out that &lt;code&gt;COPY&lt;/code&gt; is extremely flexible! with the &lt;code&gt;COPY --from&lt;/code&gt; instruction in the &lt;code&gt;Dockerfile&lt;/code&gt;&lt;br&gt;
is completely capable of pull the image from a registry and the dedicated directories can be copied easily!&lt;/p&gt;
&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Final Checks for &lt;code&gt;Dockerfile&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; mcr.microsoft.com/dotnet/aspnet:6.0-focal&lt;/span&gt;

&lt;span class="c"&gt;# Add those missing SDKs here&lt;/span&gt;
&lt;span class="c"&gt;## Syntax: COPY --from=&amp;lt;registry/image:version&amp;gt; image_directory ProdStage__dest_directory&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=mcr.microsoft.com/dotnet/sdk:2.1 /usr/share/dotnet /usr/share/dotnet/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=mcr.microsoft.com/dotnet/sdk:5.1 /usr/share/dotnet /usr/share/dotnet&lt;/span&gt;

&lt;span class="c"&gt;# List all the SDKs / Runtimes available in the container&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["dotnet", "--list-sdks", "dotnet", "--list-runtimes"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;build the image and run it and the output will be similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Microsoft.AspNetCore.All 2.1.30 [/usr/share/dotnet/shared/Microsoft.AspNetCore.All]
Microsoft.AspNetCore.App 2.1.30 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.17 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.6 [/usr/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 2.1.30 [/usr/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.17 [/usr/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.6 [/usr/share/dotnet/shared/Microsoft.NETCore.App]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Voilá ! Runtimes &lt;code&gt;v6.0&lt;/code&gt;, &lt;code&gt;v5.0&lt;/code&gt;, &lt;code&gt;v2.1&lt;/code&gt; all in the same container !!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final &lt;code&gt;Dockerfile&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; mcr.microsoft.com/dotnet/aspnet:6.0-focal&lt;/span&gt;

&lt;span class="c"&gt;# Add those missing SDKs here&lt;/span&gt;
&lt;span class="c"&gt;## Syntax: COPY --from=&amp;lt;registry/image:version&amp;gt; image_directory ProdStage__dest_directory&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=mcr.microsoft.com/dotnet/sdk:2.1 /usr/share/dotnet /usr/share/dotnet/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=mcr.microsoft.com/dotnet/sdk:5.1 /usr/share/dotnet /usr/share/dotnet&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /DotNetVoyage&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./appsettings.json /DotNetVoyage/Server-Files/&lt;/span&gt;

&lt;span class="c"&gt;# Copy every '.dll' file I was handed &lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; ./* /DotNetVoyage/Server-Files/&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; [ "dotnet", "Server-Files/Server.dll" ]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Dedicated &lt;a href="https://stackoverflow.com/a/72745810/4851126"&gt;StackOverflow Query I created and answered&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Just another day on the decks with Docker !!&lt;/p&gt;

&lt;p&gt;Get in touch if you feel like dropping on suggestions, feedbacks or criticism!&lt;/p&gt;

</description>
      <category>docker</category>
      <category>dotnet</category>
      <category>microservices</category>
    </item>
    <item>
      <title>Use htpasswd to secure your node-RED Container</title>
      <dc:creator>Shan Desai</dc:creator>
      <pubDate>Thu, 09 Jun 2022 21:10:01 +0000</pubDate>
      <link>https://dev.to/shandesai/use-htpasswd-to-secure-your-node-red-container-15cl</link>
      <guid>https://dev.to/shandesai/use-htpasswd-to-secure-your-node-red-container-15cl</guid>
      <description>&lt;h2&gt;
  
  
  Securing Node-RED
&lt;/h2&gt;

&lt;p&gt;node-RED containers can be made secure when using Admin Authorization features&lt;br&gt;
using its &lt;a href="https://github.com/node-red/node-red/blob/master/packages/node_modules/node-red/settings.js"&gt;&lt;code&gt;settings.js&lt;/code&gt; file&lt;/a&gt; and mounting it to the container. Sounds quite simple,&lt;br&gt;
however unlike other containers that let your configure their environments via environment&lt;br&gt;
variables whereby the stored credentials are in plaintext, node-RED passwords typically are&lt;br&gt;
stored in encrypted format i.e., &lt;a href="https://en.wikipedia.org/wiki/Bcrypt"&gt;bcrypt&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Automation: Encrypted Passwords
&lt;/h2&gt;

&lt;p&gt;There is a possibility of obtaining the hashed (encrypted) password using an ephemeral container&lt;br&gt;
using:&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;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;--entrypoint&lt;/span&gt; /bin/bash nodered/node-red:2.2.2-12-minima
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One in the container use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bash-5.0&lt;span class="nv"&gt;$ &lt;/span&gt;node-red-admin hash-pw
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which will prompt you to enter the password and the resultant hash password will be available for you&lt;br&gt;
to use.&lt;/p&gt;

&lt;p&gt;This is however, a bit more of manual work and in case where you wish to perform more easy, automated&lt;br&gt;
setups we might need to find a different way.&lt;/p&gt;
&lt;h3&gt;
  
  
  Automation Script via &lt;code&gt;htpasswd&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Apache provide a great CLI tool called &lt;code&gt;htpasswd&lt;/code&gt; that can encrypt our plaintext passwords&lt;br&gt;
into the required &lt;code&gt;bcrypt&lt;/code&gt; hashed format which the node-RED UI and Admin Auth backend will decrypt.&lt;/p&gt;

&lt;p&gt;For most of the distributions the package name is &lt;code&gt;apache2-utils&lt;/code&gt; so for Ubuntu/Debian you can install&lt;br&gt;
&lt;code&gt;htpasswd&lt;/code&gt; using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;apache2-utils
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Jumping right into it!
&lt;/h3&gt;

&lt;p&gt;You can go through the &lt;code&gt;man&lt;/code&gt; pages of &lt;code&gt;htpasswd&lt;/code&gt; to find how it might suit for your needs. I am cutting &lt;br&gt;
to the chase as to how I used it.&lt;/p&gt;

&lt;p&gt;In order to generate my hashed password that will be compatible with node-RED's decryption backend, I had&lt;br&gt;
to find the total Computation Cost (rounds) needed, which I found out the value was &lt;code&gt;8&lt;/code&gt; based on some &lt;br&gt;
code diving using &lt;a href="https://github.com/node-red/node-red-admin/blob/master/lib/commands/hash.js#L28"&gt;node-red-admin&lt;/a&gt; repository.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;htpasswd &lt;span class="nt"&gt;-nb&lt;/span&gt; &lt;span class="nt"&gt;-B&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; 8 admin testpassword
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;will produce the following result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;admin:&lt;span class="nv"&gt;$2y$08$JTOJDi&lt;/span&gt;/whZxz8qwsdwCkfedhVOhh00QiKHICdHLImGS8XM2v.fng2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Creating an Environment Variable Template file for node-RED Docker Container
&lt;/h3&gt;

&lt;p&gt;we will make the automation a bit more easier for us, whereby we can use &lt;code&gt;envsubst&lt;/code&gt; commandline to fill&lt;br&gt;
and environment variable called &lt;code&gt;NODERED_AUTH_ADMIN&lt;/code&gt; in a file called &lt;code&gt;node-red.env&lt;/code&gt;. But in order leverage&lt;br&gt;
the power of &lt;code&gt;envsubst&lt;/code&gt; we will create a template file called &lt;code&gt;node-red.tpl.env&lt;/code&gt; which has the following &lt;br&gt;
content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;node-red.tpl.env
&lt;span class="nv"&gt;NODERED_AUTH_ADMIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'${NODERED_AUTH_ADMIN}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We create simple bash script which will do the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Accept a plaintext password as an argument to the script&lt;/li&gt;
&lt;li&gt;use this plaintext password and hash it with &lt;code&gt;htpasswd&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;export the variable which has the result of &lt;code&gt;htpasswd&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;use the exported variable to replace the &lt;code&gt;node-red.tpl.env&lt;/code&gt; file to &lt;code&gt;node-red.env&lt;/code&gt; with the value&lt;/li&gt;
&lt;li&gt;unset variable&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The script is as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;nodeREDPwgen.sh
&lt;span class="c"&gt;#!/bin/env bash&lt;/span&gt;

&lt;span class="k"&gt;function &lt;/span&gt;nodeREDPW &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;# 1st argument to this script is plaintext password&lt;/span&gt;
    &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;NODERED_AUTH_ADMIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;htpasswd &lt;span class="nt"&gt;-nb&lt;/span&gt; &lt;span class="nt"&gt;-B&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; 8 admin &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;export &lt;/span&gt;NODERED_AUTH_ADMIN
    &lt;span class="nb"&gt;touch &lt;/span&gt;node-red.env
    envsubst &lt;span class="s1"&gt;'${NODERED_AUTH_ADMIN}'&lt;/span&gt; &amp;lt; node-red.tpl.env &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; node-red.env
    &lt;span class="nb"&gt;unset &lt;/span&gt;NODERED_AUTH_ADMIN 
&lt;span class="o"&gt;}&lt;/span&gt;

nodeREDPW &lt;span class="nv"&gt;$1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x nodeREDPwgen.sh

./nodeREDPWgen.sh testpasswd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;will produce a &lt;code&gt;node-red.env&lt;/code&gt; file after successful execution with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NODERED_AUTH_ADMIN='admin:$2y$08$/8NVmF2gJ3JDrmv0tX2ud.P7S9gqvB0ds2cRByhnZcRdZOWhTnPN.'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Creating a &lt;code&gt;settings.js&lt;/code&gt; file for Node-RED Container
&lt;/h3&gt;

&lt;p&gt;copy the &lt;a href="https://github.com/node-red/node-red/blob/master/packages/node_modules/node-red/settings.js"&gt;&lt;code&gt;settings.js&lt;/code&gt;&lt;/a&gt; file from the node-red repository and adapt the &lt;code&gt;adminAuth&lt;/code&gt; object&lt;br&gt;
as follows:&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="nx"&gt;adminAuth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;credentials&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODERED_AUTH_ADMIN&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODERED_AUTH_ADMIN&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
      &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODERED_AUTH_ADMIN&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODERED_AUTH_ADMIN&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}]&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;h3&gt;
  
  
  Creating a &lt;code&gt;docker-compose.yml&lt;/code&gt; file
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;node-red&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nodered/node-red:2.2.2-12-minimal&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secure_nodered&lt;/span&gt;
    &lt;span class="na"&gt;env_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./node-red.env&lt;/span&gt;
    &lt;span class="na"&gt;ports&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;1880:1880"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./settings.js:/data/settings.js&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;bring the container up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The UI is available at &lt;code&gt;http://localhost:1880&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;If you try to login in by typing &lt;code&gt;testpassword&lt;/code&gt; in the Admin UI, you will get Login Failed! But Why?&lt;/p&gt;

&lt;h3&gt;
  
  
  Investigations
&lt;/h3&gt;

&lt;p&gt;It turns out that the password generated by &lt;code&gt;htpasswd&lt;/code&gt; produces a variant of bcrypt &lt;code&gt;$2y&lt;/code&gt; where as,&lt;br&gt;
if we were to generate the password using &lt;code&gt;node-red-admin hash-pw&lt;/code&gt; and added &lt;code&gt;testpassword&lt;/code&gt; it would&lt;br&gt;
generate a completely different looking password with the variant &lt;code&gt;$2b&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Example: &lt;code&gt;$2b$08$sfPlvPNDVVz/duAbAC2bbuNEVuvrwmU01x7plOAQIem6H187Xxq9G&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Solution
&lt;/h3&gt;

&lt;p&gt;It turns out that the &lt;code&gt;$2y&lt;/code&gt; variant of bcrypt has compatibiltity and we could simply try and replace&lt;br&gt;
&lt;code&gt;$2y&lt;/code&gt; to &lt;code&gt;$2b&lt;/code&gt; in our &lt;code&gt;settings.js&lt;/code&gt; as follows:&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="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// .. removed for brewity&lt;/span&gt;
  &lt;span class="na"&gt;adminAuth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;credentials&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODERED_AUTH_ADMIN&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODERED_AUTH_ADMIN&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
      &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODERED_AUTH_ADMIN&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODERED_AUTH_ADMIN&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$2y$&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$2b$&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// removed for brewity&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;notice the &lt;code&gt;.replace("$2y", "$2b")&lt;/code&gt; at the end of &lt;code&gt;password&lt;/code&gt; key.&lt;/p&gt;

&lt;p&gt;Let's spin the container up again using &lt;code&gt;docker compose up -d&lt;/code&gt; and try to login with &lt;code&gt;testpassword&lt;/code&gt; et Voila!!&lt;/p&gt;

&lt;p&gt;We are able to login into Node-RED!&lt;/p&gt;

&lt;p&gt;I found that &lt;strong&gt;Nick O'Leary&lt;/strong&gt; (founder of Node-RED and the great guy that he is..) already did &lt;a href="https://github.com/node-red/node-red-docker/issues/109"&gt;some research on this&lt;/a&gt;,&lt;br&gt;
I just had to bring this all together.&lt;/p&gt;

&lt;p&gt;If you have some suggestions, critiques than please get in touch with me and I would be happy to help.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/shantanoo-desai/0d5fbc51518f6cdd1e04cbeab8d63c13"&gt;GitHub Gist of the files&lt;/a&gt; for the impatient ones!&lt;/p&gt;

</description>
      <category>docker</category>
      <category>security</category>
      <category>javascript</category>
      <category>devops</category>
    </item>
    <item>
      <title>Set the hostname of a Docker Container same as that of your host machine</title>
      <dc:creator>Shan Desai</dc:creator>
      <pubDate>Wed, 08 Jun 2022 19:02:30 +0000</pubDate>
      <link>https://dev.to/shandesai/set-the-hostname-of-a-docker-container-same-as-that-of-your-host-machine-26h5</link>
      <guid>https://dev.to/shandesai/set-the-hostname-of-a-docker-container-same-as-that-of-your-host-machine-26h5</guid>
      <description>&lt;h2&gt;
  
  
  Setting Container Hostname to be same as your Host Machine
&lt;/h2&gt;

&lt;p&gt;Sounds trivial, but it turns out you need to do some tweaks in order to set the hostname&lt;br&gt;
to be the same as that of Host Machine.&lt;/p&gt;
&lt;h3&gt;
  
  
  Scenario
&lt;/h3&gt;

&lt;p&gt;I had a requirement at work, where I needed to set the hostname of a specific container&lt;br&gt;
to be that of the Machine it was supposed to run on.&lt;/p&gt;

&lt;p&gt;The Linux Distribution under consideration was &lt;strong&gt;Ubuntu 20.04 LTS&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Ubuntu's mysterious &lt;code&gt;HOST&lt;/code&gt; and &lt;code&gt;HOSTNAME&lt;/code&gt; environment variables
&lt;/h3&gt;

&lt;p&gt;If you are on an Ubuntu machine right now, try &lt;code&gt;echo $HOS&lt;/code&gt; and press the TAB key to let the bash completion fill out. High chances that you will see &lt;code&gt;HOST&lt;/code&gt; and &lt;code&gt;HOSTNAME&lt;/code&gt;&lt;br&gt;
as available Environment Variables already available to your bash shell's session.&lt;/p&gt;

&lt;p&gt;Fairly Simple, you could simply use either one of them in your Compose file as an environment variable to the &lt;code&gt;hostname&lt;/code&gt; key and should work out of the box! Not quite!&lt;/p&gt;
&lt;h3&gt;
  
  
  Investigation via a simple Example
&lt;/h3&gt;

&lt;p&gt;Take the following &lt;code&gt;docker-compose.yml&lt;/code&gt; file&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="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;alpine:latest&lt;/span&gt;
    &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hostname-tester&lt;/span&gt;
    &lt;span class="na"&gt;hostname&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;alpine-${HOST}&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;hostname&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The following &lt;code&gt;test&lt;/code&gt; service should be able to retrieve the &lt;code&gt;HOST&lt;/code&gt; variable value and set it as the hostname of the alpine container. The &lt;code&gt;command&lt;/code&gt; should be able to print something like &lt;code&gt;alpine-my-ubuntu&lt;/code&gt; as an example.&lt;/p&gt;

&lt;p&gt;Let's see what happens when we run &lt;code&gt;docker compose up&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;WARN[0000] The &lt;span class="s2"&gt;"HOST"&lt;/span&gt; variable is not set. Defaulting to a blank string. 
&lt;span class="o"&gt;[&lt;/span&gt;+] Running 1/1
 ⠿ Container hostname-tester  Recreated                                    0.1s
Attaching to hostname-tester
hostname-tester  | alpine-
hostname-tester exited with code 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you see the &lt;code&gt;HOST&lt;/code&gt; variable although available to the shell is not available within the &lt;br&gt;
Compose file.&lt;/p&gt;

&lt;p&gt;Most of the shell environment variables are visible via using &lt;code&gt;printenv&lt;/code&gt; on the host machine, so a quick search for &lt;code&gt;HOST&lt;/code&gt; or &lt;code&gt;HOSTNAME&lt;/code&gt; reveals that these specific environment variables are not in the &lt;code&gt;env&lt;/code&gt; of the shell&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;printenv&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"host"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Solution
&lt;/h3&gt;

&lt;p&gt;The most standard way to make a variable available for a shell session is by exporting it.&lt;/p&gt;

&lt;p&gt;Let's export &lt;code&gt;HOST&lt;/code&gt; using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;HOST
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's try bringing the container back up again and see if it picks up the variable values&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ &lt;span class="nb"&gt;export &lt;/span&gt;HOST
❯ docker compose up
&lt;span class="o"&gt;[&lt;/span&gt;+] Running 1/0
 ⠿ Container hostname-tester  Recreated                                    0.0s
Attaching to hostname-tester
hostname-tester  | alpine-shan-pc
hostname-tester exited with code 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;for my machine (&lt;strong&gt;Manjaro Linux (Rolling)&lt;/strong&gt;) I could now set the hostname of the container to be the same as that of the Host Machine.&lt;/p&gt;

&lt;p&gt;Sure enough searching through &lt;code&gt;printenv&lt;/code&gt; again you will find the export &lt;code&gt;HOST&lt;/code&gt; variable.&lt;/p&gt;

&lt;p&gt;If you have on-prem servers or cloud images where you might need such a configuration a solution would be to add &lt;code&gt;export HOST&lt;/code&gt; to your user's &lt;code&gt;~/.bashrc&lt;/code&gt; or &lt;code&gt;~/.zshrc&lt;/code&gt; (if using ZSH)&lt;br&gt;
and the value will be available when bringing the corresponding Compose Stack up.&lt;/p&gt;

&lt;p&gt;Hope this information helps people trying to find a similar solutions when it comes to Docker and Docker Compose based environments.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>devops</category>
      <category>linux</category>
    </item>
    <item>
      <title>No Setup Development: Productivity Experience with Docker</title>
      <dc:creator>Shan Desai</dc:creator>
      <pubDate>Sun, 08 May 2022 09:13:47 +0000</pubDate>
      <link>https://dev.to/shandesai/no-setup-development-productivity-experience-with-docker-2pbf</link>
      <guid>https://dev.to/shandesai/no-setup-development-productivity-experience-with-docker-2pbf</guid>
      <description>&lt;h2&gt;
  
  
  Why I stopped worrying about setting up environments!
&lt;/h2&gt;

&lt;p&gt;If Stanley Kubrick were a Software Engineer, he would have named this post&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Dr. Nosetup: How I Stopped Worrying About Setting Up and Love the Development&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;(I'll see myself out with that pun!)&lt;/p&gt;

&lt;p&gt;I tried contributing to an open-source project without actually setting up the&lt;br&gt;
complete programming language tools, and it felt like worth documenting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem: So much to download, and setup before getting to work
&lt;/h3&gt;

&lt;p&gt;I tried sending a feature to the &lt;a href="https://github.com/node-red/node-red/pull/3599"&gt;node-red GitHub Repository with a new TOML configuration node&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However, I didn't want to &lt;em&gt;taint&lt;/em&gt; (pardon me for using the word) my personal laptop by installing&lt;br&gt;
&lt;code&gt;node.js&lt;/code&gt; and &lt;code&gt;npm&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;One particular reason being, is that I have less time now to continue with Web Development stuff,&lt;br&gt;
and &lt;code&gt;node.js&lt;/code&gt; isn't my preferred language anyways. I want my host laptop to be as minimal as possible.&lt;/p&gt;

&lt;p&gt;But I wanted to send the feature patch upstream because I was &lt;em&gt;in the zone&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution: Docker encapsulated Environment
&lt;/h3&gt;

&lt;p&gt;Since I have been heavily using &lt;code&gt;docker&lt;/code&gt; for a while now, I asked myself&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;What do I need to send a patch upstream?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A: Only relevant files&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Does &lt;code&gt;docker&lt;/code&gt; provide me a &lt;code&gt;node.js&lt;/code&gt; environment?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A: Yes, certainly it does. Not just &lt;code&gt;node.js&lt;/code&gt; but for all possible programming languages&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How do I avoid doing &lt;em&gt;manual copy-paste&lt;/em&gt; labour for files between the container and my laptop?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A: &lt;strong&gt;Volume-Mounts&lt;/strong&gt;. Any changes within the containe get reflected back to the host laptop and vice-versa&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting it Up!
&lt;/h2&gt;

&lt;p&gt;All I really needed was &lt;code&gt;docker&lt;/code&gt; on my machine and we are ready to go!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Steps&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;clone the repository to dedicated directory on my host laptop&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Visit &lt;strong&gt;Docker Hub&lt;/strong&gt; and find the &lt;code&gt;node-js&lt;/code&gt; Image repository&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Find the Long-Term-Support (LTS) version image tag. In my case it was &lt;code&gt;16.15.0&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So we almost have everything we want!&lt;/p&gt;

&lt;h3&gt;
  
  
  Caveats
&lt;/h3&gt;

&lt;p&gt;Remember that Docker Containers are their own &lt;em&gt;ephemeral&lt;/em&gt; worlds all together.&lt;/p&gt;

&lt;p&gt;If the containers are designed to with &lt;code&gt;root&lt;/code&gt; users, your files may change ownership, or might have&lt;br&gt;
different owners. You could check this using &lt;code&gt;ls -la&lt;/code&gt; in your directory.&lt;/p&gt;

&lt;p&gt;I really want to avoid such scenarios, such ownership issues might affect your filesystem as well as&lt;br&gt;
the upstream code. But no issues, &lt;code&gt;docker&lt;/code&gt; CLI provides way to control the user and group settings &lt;br&gt;
before bringing the container up.&lt;/p&gt;

&lt;p&gt;It is also worth mentioning that, the container environments also produce files that should be not be&lt;br&gt;
reflected in your commits upstream. In the case of &lt;code&gt;node-red&lt;/code&gt; the &lt;code&gt;package-lock.json&lt;/code&gt; is a file created&lt;br&gt;
within the container that will be mapped to the host machine.&lt;/p&gt;

&lt;p&gt;It might be wise to keep such files into &lt;code&gt;.gitignore&lt;/code&gt; as well as &lt;code&gt;.dockerignore&lt;/code&gt; files within the development&lt;br&gt;
repository to avoid accidently committing them upstream or bringing them within the container.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker CLI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="c"&gt;# assuming your are in the development repository&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;node-red-TOML &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;:&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\&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;:/usr/src/app &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-p&lt;/span&gt; 1880:1880 &lt;span class="se"&gt;\&lt;/span&gt;
     node:16.15.0 &lt;span class="se"&gt;\&lt;/span&gt;
     /bin/bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-u&lt;/code&gt; parameter maps your current user id and group to the container, avoiding any &lt;code&gt;root&lt;/code&gt; ownership&lt;br&gt;
conflicts.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;-v&lt;/code&gt; parameter is the volume mount that will map the codebase to the &lt;code&gt;/usr/src/app&lt;/code&gt; directory in the&lt;br&gt;
container.&lt;/p&gt;

&lt;p&gt;There you have it ! A node-js environment without having to download and setup the tool on the host!&lt;/p&gt;

&lt;p&gt;You can now code everything with ease with your Editor of your choice with the container running.&lt;/p&gt;

&lt;p&gt;Any changes, either on the host or within the container will be reflected to your editor.&lt;/p&gt;

&lt;p&gt;Just make sure to run the execution commands in the container.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits
&lt;/h2&gt;

&lt;p&gt;This worked out well for me! I was able to get the codebase up and running in no time, without having to&lt;br&gt;
worry about incompatibility issues.&lt;/p&gt;

&lt;p&gt;Changes made in my editor (new files, refactored files) are available in the container to use and execute.&lt;/p&gt;

&lt;p&gt;Running the commands within the container makes it easier to know what happens and all of this is &lt;em&gt;ephemeral&lt;/em&gt;&lt;br&gt;
so I don't have to do a lot of cleanup afterwards.&lt;/p&gt;

&lt;p&gt;Just remove the container and commit the code!&lt;/p&gt;

&lt;p&gt;On a side-note, the &lt;a href="https://github.com/node-red/node-red/pull/3599"&gt;feature patch upstream&lt;/a&gt; wasn't required by the core team :(, but I could use the &lt;br&gt;
same development environment pattern to create a &lt;code&gt;node-red-contrib&lt;/code&gt; node. So nothing does to waste!&lt;/p&gt;

&lt;p&gt;Hope this helps, get in touch if you would like to provide some suggestions, criticisms!&lt;/p&gt;

</description>
      <category>docker</category>
      <category>node</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
