<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[hatem ben tayeb]]></title><description><![CDATA[Do it Once Then Automate It ]]></description><link>https://blog.hatembentayeb.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/5ff1aabe45519f71a3336aab/8189df0d-787f-4def-904d-4d8ce3ceebff.png</url><title>hatem ben tayeb</title><link>https://blog.hatembentayeb.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Mon, 27 Apr 2026 15:21:00 GMT</lastBuildDate><atom:link href="https://blog.hatembentayeb.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Scale your containers with ansible on a non kubernetes/swarm environment]]></title><description><![CDATA[Scaling containers on Kubernetes with Replicatsets or scale services with docker swarm is a very easy task! but what about scaling containers in a non managed environment, when you have high traffic, absolutely you need to scale and load balance the ...]]></description><link>https://blog.hatembentayeb.dev/scale-your-containers-with-ansible-on-a-non-kubernetesswarm-environment</link><guid isPermaLink="true">https://blog.hatembentayeb.dev/scale-your-containers-with-ansible-on-a-non-kubernetesswarm-environment</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[ansible]]></category><category><![CDATA[Docker]]></category><category><![CDATA[nginx]]></category><category><![CDATA[scalability]]></category><dc:creator><![CDATA[hatem ben tayeb]]></dc:creator><pubDate>Sat, 16 Jan 2021 18:50:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1610823359948/oK1ukWave.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>Scaling containers on Kubernetes with Replicatsets or scale services with docker swarm is a very easy task! but what about scaling containers in a non managed environment, when you have high traffic, absolutely you need to scale and load balance the traffic among multiple containers. In this article, we will use Ansible and nginx to scale containers.</p>
</blockquote>
<h1 id="setting-up-the-environment">Setting up the environment</h1>
<p>Before starting make sure you have the required packages: </p>
<ul>
<li>Ansible 2.9: <code>pip install ansible</code></li>
<li>Docker latest : follow this <a target="_blank" href="https://phoenixnap.com/kb/how-to-install-docker-on-ubuntu-18-04">link</a> to install docker </li>
<li>Docker python package installed on the remote host: <code>pip install docker</code></li>
<li>A ubuntu machine (18.04)</li>
</ul>
<h1 id="lets-code">Let's Code!</h1>
<p>In this section, we will attach the puzzle pieces, we will test everything locally, but the actual aim of this setup is to integrate it into the pipeline, so we can put the scale number and deploy it if you don't want to make it in the CI/CD pipeline, it's ok you can just launch the playbook against the server and scale your container (don't forget the container name), automatically will scale the containers and update the nginx config.</p>
<h2 id="inventory-file">Inventory file</h2>
<p>The inventory file will hold our variables and the target where we can  execute the task, it contains two host children (network and clean)  derived from the main host (local) </p>
<pre><code class="lang-toml"><span class="hljs-section">[local]</span>
localhost <span class="hljs-attr">ansible_connection</span>=local
<span class="hljs-section">[local:vars]</span>
<span class="hljs-attr">scale</span>=<span class="hljs-number">5</span>
<span class="hljs-attr">c_name</span>=&lt;container-name&gt;
<span class="hljs-attr">h_name</span>=&lt;container-name&gt;
<span class="hljs-attr">subnet</span>=<span class="hljs-number">172.16</span>.<span class="hljs-number">0</span>                                           
<span class="hljs-attr">network_name</span>=network
<span class="hljs-attr">image_name</span>=&lt;image-name&gt;
<span class="hljs-section">[network:children]</span>
local
<span class="hljs-section">[clean:children]</span>
local
</code></pre>
<p>The most important variable is <code>scale</code>, it's the number of containers that will start in the host, the other variables are about the container name, the image, and the subnet where the containers will run.</p>
<h2 id="ansible-playbook">Ansible playbook</h2>
<p>The ansible-playbook will hold all the logic of what we will do here. the inventory files variables will be accessible to the playbook </p>
<h3 id="creating-the-network">Creating the network</h3>
<p>This task will create the network with the specified subnet and the gateway, note that the network will be created if doesn't exist, otherwise, the task will be skipped </p>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">hosts:</span> <span class="hljs-string">network</span>
  <span class="hljs-attr">tasks:</span>                                                     
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">docker</span> <span class="hljs-string">network</span>
    <span class="hljs-attr">docker_network:</span>
      <span class="hljs-attr">name:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ network_name }}</span>"</span>
      <span class="hljs-attr">ipam_config:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">subnet:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ subnet }}</span>.0/16"</span>
          <span class="hljs-attr">gateway:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ subnet }}</span>.1"</span>
</code></pre>
<h3 id="getting-the-last-scale-number">Getting the last scale number</h3>
<p>Based on the image name we can get the number of containers actually running, to do that we used a shell script the gather that information. The <code>set_fact</code> will create a variable dynamically that will be accessible to the whole playbook by registering the output of the script and get the stdout as a JSON format. </p>
<pre><code class="lang-yaml">
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">get</span> <span class="hljs-string">last</span> <span class="hljs-string">scale</span>
    <span class="hljs-attr">script:</span> <span class="hljs-string">./get_cont_num.sh</span> {{ <span class="hljs-string">image_name</span> }}
    <span class="hljs-attr">register:</span> <span class="hljs-string">last_scale</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">set_fact:</span> 
      <span class="hljs-string">last_scale={{</span> <span class="hljs-string">last_scale.stdout</span> <span class="hljs-string">}}</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">debug:</span> <span class="hljs-string">var=last_scale</span>
</code></pre>
<p>We can debug the variable content by adding the <code>debug</code> keyword and print it out.</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/bash </span>

docker ps -f ancestor=<span class="hljs-variable">$1</span> --format <span class="hljs-string">'{{.Names}}'</span> | wc -l
</code></pre>
<p>This bash script will give us names like this <code>name_1,name2 ..etc</code> so we can count it with the word count command <code>wc</code> the line option.</p>
<h3 id="starting-the-container">Starting the container</h3>
<p>Starting the container with the actual number specified in the <code>scale</code> variable, looping into it with the <code>with_sequence</code> and index the counter with <code>item</code>, For the <code>ipv4_address</code> it starts from <code>2</code> because the first one is for the gateway. </p>
<pre><code class="lang-yaml"> <span class="hljs-bullet">-</span> <span class="hljs-attr">name :</span> <span class="hljs-string">starting</span> <span class="hljs-string">containers</span>
    <span class="hljs-attr">docker_container:</span>
      <span class="hljs-attr">name:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ c_name }}</span>_<span class="hljs-template-variable">{{ item }}</span>"</span>
      <span class="hljs-attr">image :</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ image_name }}</span>"</span>
      <span class="hljs-attr">pull:</span> <span class="hljs-literal">yes</span>
      <span class="hljs-attr">restart_policy:</span> <span class="hljs-string">always</span>
      <span class="hljs-attr">hostname:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ h_name }}</span>_<span class="hljs-template-variable">{{ item }}</span>"</span>
      <span class="hljs-attr">networks:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ network_name }}</span>"</span>
          <span class="hljs-attr">ipv4_address:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ subnet }}</span>.<span class="hljs-template-variable">{{ 1+item|int }}</span>"</span>
      <span class="hljs-attr">purge_networks:</span> <span class="hljs-literal">yes</span>
    <span class="hljs-attr">with_sequence:</span> <span class="hljs-string">start=1</span> <span class="hljs-string">end="{{</span> <span class="hljs-string">scale</span> <span class="hljs-string">}}"</span>
</code></pre>
<p>If you have many containers on the network make sure to separate the IPs (host ID) with al least 5 like this : </p>
<pre><code><span class="hljs-attr">Frontend :</span> <span class="hljs-number">172.16</span><span class="hljs-number">.0</span><span class="hljs-number">.2</span>
<span class="hljs-attr">Backend:</span> <span class="hljs-number">172.16</span><span class="hljs-number">.0</span><span class="hljs-number">.8</span>
<span class="hljs-string">...</span>
</code></pre><p>To be able to scale each container and don't make an IP conflict when scaling.</p>
<h3 id="removing-container">Removing container</h3>
<p>In this task, we will remove the unneeded containers, it starts only when the <code>scale number &gt; last scale</code> otherwise the task will be skipped. but we have to make things clear on the <code>with_sequence</code> logic because it will be compiled but not executed when skipping.</p>
<pre><code class="lang-yaml">  <span class="hljs-bullet">-</span> <span class="hljs-attr">name :</span> <span class="hljs-string">down-scaling</span> <span class="hljs-string">uneeded</span> <span class="hljs-string">containers</span> 
    <span class="hljs-attr">docker_container:</span> 
      <span class="hljs-attr">name:</span> <span class="hljs-string">mongo_{{</span> <span class="hljs-string">item</span> <span class="hljs-string">}}</span>
      <span class="hljs-attr">state:</span> <span class="hljs-string">absent</span>
    <span class="hljs-attr">with_sequence:</span> <span class="hljs-string">start="{{</span> <span class="hljs-string">last_scale|int</span> <span class="hljs-string">if</span> <span class="hljs-string">(last_scale|int</span> <span class="hljs-bullet">-</span> <span class="hljs-string">scale|int)|abs</span> <span class="hljs-string">==</span> <span class="hljs-number">1</span><span class="hljs-string">|int</span>  <span class="hljs-string">or</span> <span class="hljs-string">last_scale|int</span> <span class="hljs-string">==</span> <span class="hljs-string">scale|int</span>  <span class="hljs-string">or</span> <span class="hljs-string">scale|int</span> <span class="hljs-string">&gt;</span> <span class="hljs-string">last_scale|int</span> <span class="hljs-string">else</span> <span class="hljs-number">1</span><span class="hljs-string">+scale|int</span>  <span class="hljs-string">}}"</span> <span class="hljs-string">end="{{</span> <span class="hljs-string">last_scale</span> <span class="hljs-string">}}"</span> 
    <span class="hljs-comment"># last_scale=4 scale=2</span>
    <span class="hljs-comment"># last_scale=3 scale=3</span>
    <span class="hljs-comment"># last_scale=3 scale=4 </span>
    <span class="hljs-comment"># last_scale=2 scale=4</span>
    <span class="hljs-attr">when:</span> <span class="hljs-string">scale|int</span> <span class="hljs-string">&lt;</span> <span class="hljs-string">last_scale|int</span>
</code></pre>
<p>The logic says that if : 
==&gt; return last_scale <code>if</code> </p>
<ul>
<li>Positive(last_scale - scale) == 1 <code>or</code></li>
<li>last_scale == scale <code>or</code></li>
<li>scale &gt; last_scale <code>else</code></li>
<li>return 1 + scale<br />This logic can ensure removing the right number of containers without mistakes </li>
</ul>
<h3 id="getting-containers-ips">Getting containers IPs</h3>
<p>In this task we will get all IPs of our scaled containers. The set fast will contain all IPs by filtering the output from <code>register</code> by splitting with <code>r\n</code> to get a loopable list </p>
<pre><code class="lang-yaml">  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">generate</span>  <span class="hljs-string">ip</span> 
    <span class="hljs-attr">script:</span> <span class="hljs-string">./get_name_ip.sh</span> {{ <span class="hljs-string">scale</span> }}
    <span class="hljs-attr">register:</span> <span class="hljs-string">last_scale</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">set_fact:</span> 
        <span class="hljs-string">list_ip={{</span> <span class="hljs-string">last_scale.stdout.split("\r\n")|list</span> <span class="hljs-string">}}</span>
</code></pre>
<p>Depending on the scale number we can loop over the container names (name_1,name_2) and get all IPs associated with every container.</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/bash</span>
scale=<span class="hljs-variable">$1</span>

<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> $(seq <span class="hljs-variable">$scale</span>) 
<span class="hljs-keyword">do</span>
docker inspect container_<span class="hljs-variable">$i</span> | jq <span class="hljs-string">".[].NetworkSettings.Networks.network.IPAddress"</span> -r
<span class="hljs-keyword">done</span>
</code></pre>
<h3 id="generating-nginxconf-file">Generating nginx.conf file</h3>
<p>Thanks to the <code>set_fact</code> all variables that are created in the playbook dynamically will be exposed by default to the <code>jinja2</code> template so we can use it easily. </p>
<pre><code class="lang-yaml">  <span class="hljs-bullet">-</span> <span class="hljs-attr">name :</span> <span class="hljs-string">generate</span> <span class="hljs-string">file</span> 
    <span class="hljs-attr">template :</span> 
        <span class="hljs-attr">src:</span> <span class="hljs-string">example.domaine.com.conf.j2</span>
        <span class="hljs-attr">dest:</span> <span class="hljs-string">example.domaine.com.conf</span>
</code></pre>
<p>The <code>template</code> module will generate a new <code>nginx.conf</code> depends on the scale number, I mean if we have <code>scale=1</code> we will generate a simple configuration, otherwise, we will make the load balancing configuration with the containers IPs </p>
<pre><code class="lang-nginx">{% <span class="hljs-attribute">if</span>  scale &gt; <span class="hljs-number">1</span> %}
upstream manager {
{% <span class="hljs-attribute">for</span> i in range(scale) -%}
server http://{{ list_ip[ <span class="hljs-attribute">i</span> ] |ipaddr }};
{% <span class="hljs-attribute">endfor</span> %}
}
server {
        <span class="hljs-attribute">listen</span> <span class="hljs-number">80</span>;
        <span class="hljs-attribute">listen</span> [::]:<span class="hljs-number">80</span>;
        <span class="hljs-attribute">server_name</span> admin.domaine.com;
        <span class="hljs-attribute">return</span> <span class="hljs-number">301</span> https://<span class="hljs-variable">$host</span><span class="hljs-variable">$request_uri</span>;
}
<span class="hljs-section">server</span> {
        <span class="hljs-attribute">listen</span> <span class="hljs-number">443</span> ssl http2;
        <span class="hljs-attribute">listen</span> [::]:<span class="hljs-number">443</span> ssl http2;
        <span class="hljs-attribute">server_name</span> admin.domaine.com;
        <span class="hljs-attribute">ssl</span> <span class="hljs-literal">on</span>;
        <span class="hljs-attribute">ssl_certificate</span> /etc/letsencrypt/live/admin.domaine.com/fullchain.pem;
        <span class="hljs-attribute">ssl_certificate_key</span> /etc/letsencrypt/live/admin.domaine.com/privkey.pem;
        <span class="hljs-attribute">add_header</span> Strict-Transport-Security <span class="hljs-string">"max-age=31536000; includeSubDomains"</span>;
        <span class="hljs-attribute">location</span> / {
                <span class="hljs-attribute">include</span> /etc/nginx/extra.d/caching_ht.conf;
                <span class="hljs-attribute">proxy_pass</span> http://manager;
                <span class="hljs-attribute">proxy_http_version</span> <span class="hljs-number">1</span>.<span class="hljs-number">1</span>;
                <span class="hljs-attribute">proxy_set_header</span> Upgrade <span class="hljs-variable">$http_upgrade</span>; <span class="hljs-comment"># allow websockets</span>
                <span class="hljs-attribute">proxy_set_header</span> X-Forwarded-For <span class="hljs-variable">$remote_addr</span>; <span class="hljs-comment"># preserve clien$</span>
                <span class="hljs-attribute">proxy_set_header</span> Host <span class="hljs-variable">$remote_addr</span>; <span class="hljs-comment"># preserve client IP</span>
        }
}
{% <span class="hljs-attribute">else</span> %}
server {
        <span class="hljs-attribute">listen</span> <span class="hljs-number">80</span>;
        <span class="hljs-attribute">listen</span> [::]:<span class="hljs-number">80</span>;
        <span class="hljs-attribute">server_name</span> admin.domaine.com;
        <span class="hljs-attribute">return</span> <span class="hljs-number">301</span> https://<span class="hljs-variable">$host</span><span class="hljs-variable">$request_uri</span>;
}
<span class="hljs-section">server</span> {
        <span class="hljs-attribute">listen</span> <span class="hljs-number">443</span> ssl http2;
        <span class="hljs-attribute">listen</span> [::]:<span class="hljs-number">443</span> ssl http2;
        <span class="hljs-attribute">server_name</span> admin.domaine.com;
        <span class="hljs-attribute">ssl</span> <span class="hljs-literal">on</span>;
        <span class="hljs-attribute">ssl_certificate</span> /etc/letsencrypt/live/admin.domaine.com/fullchain.pem;
        <span class="hljs-attribute">ssl_certificate_key</span> /etc/letsencrypt/live/admin.domaine.com/privkey.pem;
        <span class="hljs-attribute">add_header</span> Strict-Transport-Security <span class="hljs-string">"max-age=31536000; includeSubDomains"</span>;
        <span class="hljs-attribute">location</span> / {
                <span class="hljs-attribute">include</span> /etc/nginx/extra.d/caching_ht.conf;
                <span class="hljs-attribute">proxy_pass</span> http://{{ list_ip[0]|<span class="hljs-attribute">ipaddr</span> }};
                <span class="hljs-attribute">proxy_http_version</span> <span class="hljs-number">1</span>.<span class="hljs-number">1</span>;
                <span class="hljs-attribute">proxy_set_header</span> Upgrade <span class="hljs-variable">$http_upgrade</span>; <span class="hljs-comment"># allow websockets</span>
                <span class="hljs-attribute">proxy_set_header</span> X-Forwarded-For <span class="hljs-variable">$remote_addr</span>; <span class="hljs-comment"># preserve clien$</span>
                <span class="hljs-attribute">proxy_set_header</span> Host <span class="hljs-variable">$remote_addr</span>; <span class="hljs-comment"># preserve client IP</span>
        }
}
{% <span class="hljs-attribute">endif</span> %}
</code></pre>
<p>For the SSL configuration, you can make sit easily by running the certbot command and get your certificate, note that this configuration is applied to the frontend containers because we have SSL, you can apply it to your backend easily and update the nginx configurations easily.</p>
<h3 id="applying-the-nginx-configuration">Applying the nginx configuration</h3>
<p>This task is trivial, we just checking nginx syntax and reloading configurations, if you want you can change the reload by restarting nginx.</p>
<pre><code class="lang-yaml">   <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Verify</span> <span class="hljs-string">Nginx</span> <span class="hljs-string">config</span>
     <span class="hljs-attr">become:</span> <span class="hljs-literal">yes</span>
     <span class="hljs-attr">command:</span> <span class="hljs-string">nginx</span> <span class="hljs-string">-t</span>
     <span class="hljs-attr">changed_when:</span> <span class="hljs-literal">false</span>

   <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">reload</span> <span class="hljs-string">Nginx</span> <span class="hljs-string">config</span>
     <span class="hljs-attr">become:</span> <span class="hljs-literal">yes</span>
     <span class="hljs-attr">command:</span> <span class="hljs-string">nginx</span> <span class="hljs-string">-s</span> <span class="hljs-string">reload</span>
     <span class="hljs-attr">changed_when:</span> <span class="hljs-literal">false</span>
</code></pre>
<h3 id="cleanning">Cleanning</h3>
<p>Clearing unused containers and volumes or images with no tags is a best practice for making your server in a good mood :) </p>
<pre><code class="lang-yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">hosts :</span> <span class="hljs-string">clean</span>
  <span class="hljs-attr">gather_facts :</span> <span class="hljs-literal">no</span> 

  <span class="hljs-attr">tasks:</span> 

  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Removing</span> <span class="hljs-string">exited</span> <span class="hljs-string">containers</span>
    <span class="hljs-attr">shell:</span> <span class="hljs-string">docker</span> <span class="hljs-string">ps</span> <span class="hljs-string">-a</span> <span class="hljs-string">-q</span> <span class="hljs-string">-f</span> <span class="hljs-string">status=exited</span> <span class="hljs-string">|</span> <span class="hljs-string">xargs</span> <span class="hljs-string">--no-run-if-empty</span> <span class="hljs-string">docker</span> <span class="hljs-string">rm</span> <span class="hljs-string">--volumes</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Removing</span> <span class="hljs-string">untagged</span> <span class="hljs-string">images</span>
    <span class="hljs-attr">shell:</span> <span class="hljs-string">docker</span> <span class="hljs-string">images</span> <span class="hljs-string">|</span> <span class="hljs-string">awk</span> <span class="hljs-string">'/^&lt;none&gt;/ { print $3 }'</span> <span class="hljs-string">|</span> <span class="hljs-string">xargs</span> <span class="hljs-string">--no-run-if-empty</span> <span class="hljs-string">docker</span> <span class="hljs-string">rmi</span> <span class="hljs-string">-f</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Removing</span> <span class="hljs-string">volume</span> <span class="hljs-string">directories</span>
    <span class="hljs-attr">shell:</span> <span class="hljs-string">docker</span> <span class="hljs-string">volume</span> <span class="hljs-string">ls</span> <span class="hljs-string">-q</span> <span class="hljs-string">--filter="dangling=true"</span> <span class="hljs-string">|</span> <span class="hljs-string">xargs</span> <span class="hljs-string">--no-run-if-empty</span> <span class="hljs-string">docker</span> <span class="hljs-string">volume</span> <span class="hljs-string">rm</span>
</code></pre>
<p>I'm integrating this solution on my CI/CD because to scale when I push my code, it's simple and needs more work to be generic and reliable, you can find all the files on <a target="_blank" href="https://github.com/hatembentayeb/AnsibleDockerScaler">Github repository</a>. The demo gif on the repository I test it with mongo, I know that we cannot scale databases like this actually, I'm jus treating it as a container that's it.</p>
]]></content:encoded></item><item><title><![CDATA[Bash: writing a simple pod checker]]></title><description><![CDATA[I was working with Kubernetes and I just want to check my pods in the current workspace, in a funny way  😅, I will use bash for just one reason, it's the Linux native languages no need for other languages and libraries

Actually, I was inspired by a...]]></description><link>https://blog.hatembentayeb.dev/bash-writing-a-simple-pod-checker</link><guid isPermaLink="true">https://blog.hatembentayeb.dev/bash-writing-a-simple-pod-checker</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[Bash]]></category><category><![CDATA[Script]]></category><category><![CDATA[Linux]]></category><dc:creator><![CDATA[hatem ben tayeb]]></dc:creator><pubDate>Fri, 15 Jan 2021 15:50:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1610725778880/5Dvt2OmRe.gif" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>I was working with Kubernetes and I just want to check my pods in the current workspace, in a funny way  😅, I will use bash for just one reason, it's the Linux native languages no need for other languages and libraries</p>
</blockquote>
<p>Actually, I was inspired by a cool tool called <strong>popeye</strong>, you can find it <a target="_blank" href="https://github.com/derailed/popeye">here</a>. </p>
<p>Make sure you have <strong>Kubectl</strong> installed and an existing cluster as well, here is my implementation </p>
<ul>
<li><strong>Define your favorite colors</strong></li>
</ul>
<pre><code class="lang-bash">blanc=<span class="hljs-string">"\033[1;37m"</span>
gray=<span class="hljs-string">"\033[0;37m"</span>
magento=<span class="hljs-string">"\033[0;35m"</span>
red=<span class="hljs-string">"\033[1;31m"</span>
green=<span class="hljs-string">"\033[1;32m"</span>
amarillo=<span class="hljs-string">"\033[1;33m"</span>
azul=<span class="hljs-string">"\033[1;34m"</span>
rescolor=<span class="hljs-string">"\e[0m"</span>
</code></pre>
<p>You can find more about colors in bash <a target="_blank" href="https://misc.flogisoft.com/bash/tip_colors_and_formatting">here</a>.</p>
<ul>
<li><strong>Get list pods into an Array</strong></li>
</ul>
<pre><code class="lang-bash">listPods=$(kubectl get po | awk <span class="hljs-string">'NR&gt;1{print $1}'</span>)
<span class="hljs-comment">#echo "$listPods"</span>
<span class="hljs-built_in">readarray</span>  arr &lt;&lt;&lt;  <span class="hljs-variable">$listPods</span>
</code></pre>
<p><code>NR&gt;1</code> will skip the first line and <code>print $1</code> will print the first words (separated by a space) on all lines.</p>
<ul>
<li><strong>Looping over the array and check the status</strong></li>
</ul>
<pre><code class="lang-bash">ok=0
notok=0
<span class="hljs-built_in">echo</span> -e <span class="hljs-string">"\nSit Down and Wait  \U1F602 :\n"</span>
<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-variable">${arr[@]}</span>
<span class="hljs-keyword">do</span> 
<span class="hljs-built_in">echo</span> -ne <span class="hljs-string">"<span class="hljs-variable">$i</span> ... "</span> 
status=$(kubectl get po <span class="hljs-variable">$i</span> | grep <span class="hljs-variable">$i</span> | awk <span class="hljs-string">'{print $3}'</span>)
    <span class="hljs-keyword">if</span> [[ ! <span class="hljs-variable">$status</span> =~ ^Running$|^Completed$  ]]  ; <span class="hljs-keyword">then</span>
        <span class="hljs-built_in">echo</span> -e <span class="hljs-string">"\e[1;31mOh Shit !"</span><span class="hljs-variable">$rescolor</span><span class="hljs-string">""</span>
        notify-send <span class="hljs-string">"Pods Health"</span> <span class="hljs-string">"<span class="hljs-variable">$i</span> was  FUCKED"</span> -t 10000 
        <span class="hljs-built_in">let</span> notok=notok+1
    <span class="hljs-keyword">else</span>
        <span class="hljs-built_in">echo</span> -e <span class="hljs-string">"\e[1;32mOK!"</span><span class="hljs-variable">$rescolor</span><span class="hljs-string">""</span>
        <span class="hljs-comment">#notify-send "Pod $i Is Good :)"</span>
      <span class="hljs-built_in">let</span> ok=ok+1
    <span class="hljs-keyword">fi</span>
<span class="hljs-keyword">done</span>
</code></pre>
<p>The <code>ok</code> and <code>notok</code> are used to count the number of the running/not running pods , the <code>${arr[@]}</code> prints out the whole array, the <code>notify-send</code> will create a notification on your system is one of the pods are <strong>f</strong>ked up** .</p>
<ul>
<li><strong>Print out the summary</strong></li>
</ul>
<pre><code class="lang-bash"><span class="hljs-built_in">echo</span> -e <span class="hljs-string">"\nSTATS:\n"</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"+---------------+---------------+"</span>
<span class="hljs-built_in">printf</span>  <span class="hljs-string">"|<span class="hljs-variable">$green</span>%-15s<span class="hljs-variable">$rescolor</span>|<span class="hljs-variable">$red</span>%-15s<span class="hljs-variable">$rescolor</span>|\n"</span> <span class="hljs-string">"Healthy Pods"</span> <span class="hljs-string">"Unhealthy Pods"</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"+---------------+---------------+"</span>
<span class="hljs-built_in">printf</span>  <span class="hljs-string">"|%-15s|%-15s|\n"</span> <span class="hljs-string">"<span class="hljs-variable">$ok</span>"</span> <span class="hljs-string">"<span class="hljs-variable">$notok</span>"</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"+---------------+---------------+"</span>
<span class="hljs-built_in">echo</span> -e <span class="hljs-string">"\n"</span>
</code></pre>
<ul>
<li><strong>Run the script</strong></li>
</ul>
<p>Download the scripts with <code>CURL</code> : </p>
<pre><code class="lang-bash">curl https://raw.githubusercontent.com/hatembentayeb/podschecker/master/podschecker.sh --output podschecker.sh
chmod +x podschecker.sh
</code></pre>
<ul>
<li><strong>Demo</strong> </li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1610725608108/yrhW96uAh.gif" alt="PodHealth.gif" /></p>
<p>Repository: https://github.com/hatembentayeb/podschecker</p>
<p><strong>The scripts don't do much but it was a result of a boring 3 hours on this pandemic</strong>  🥲</p>
]]></content:encoded></item><item><title><![CDATA[Heroku: Deploy a dockerized Flask ML app]]></title><description><![CDATA[In this post we will deploy a simple dockerized Machine learning application written with the Flask micro-framework to the Heroku (PaaS), using one of the leading CI/CD tools, which is Gitlab!, all used tools are free and no fees in achieving this tu...]]></description><link>https://blog.hatembentayeb.dev/heroku-deploy-a-dockerized-flask-ml-app</link><guid isPermaLink="true">https://blog.hatembentayeb.dev/heroku-deploy-a-dockerized-flask-ml-app</guid><category><![CDATA[Heroku]]></category><category><![CDATA[Flask Framework]]></category><category><![CDATA[Machine Learning]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[hatem ben tayeb]]></dc:creator><pubDate>Tue, 12 Jan 2021 19:34:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1610480172494/CvkufRP9P.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>In this post we will deploy a simple dockerized Machine learning application written with the Flask micro-framework to the Heroku (PaaS), using one of the leading CI/CD tools, which is Gitlab!, all used tools are free and no fees in achieving this tutorial. </p>
</blockquote>
<h1 id="tools-to-know">Tools to know</h1>
<p>This the list of tools that we need to achieve the aim of this tutorial alongside some bash scripting knowledge </p>
<table>
<thead>
<tr>
<td>Tools</td><td>Definition</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Flask</strong></td><td>is a micro web framework written in Python. It is classified as a micro-framework because it does not require specific tools or libraries. It has no database abstraction layer, form validation, or any other existing third-party library components that provide common functions. <a target="_blank" href="https://www.fullstackpython.com/flask.html">Go farther with flask</a></td></tr>
<tr>
<td><strong>Heroku</strong></td><td>is a container-based cloud Platform as a Service (PaaS). Developers use Heroku to deploy, manage, and scale modern apps. The platform is elegant, flexible, and easy to use, offering developers the simplest path to getting their apps to market. <a target="_blank" href="https://devcenter.heroku.com/start">Go farther with heroku</a></td></tr>
<tr>
<td><strong>GitLab</strong></td><td>is a web-based DevOps lifecycle tool that provides a Git-repository manager providing wiki, issue-tracking, and continuous integration and deployment pipeline features, using an open-source license. <a target="_blank" href="https://docs.gitlab.com/">Explore Gitlab documentation</a></td></tr>
</tbody>
</table>
<h1 id="code-base">Code base</h1>
<p>Before starting let's understand what we will dockerize here! This application is a simple machine learning that exposes two routes : </p>
<ul>
<li><p><strong>/kmeans</strong>: This route accepts a JSON data format with two entries. The first one is the number of clusters you want the classifier to consider, the second one is the text that we will group into the specified number of cluster, each group will contain words that are similar on the meaning or has the same area like <strong>people</strong>, <strong>building</strong>, <strong>civilization</strong> and so on.</p>
</li>
<li><p><strong>/ads </strong>: This route accepts also  JSON data format that contains also two entries which are the <strong>age</strong> and the <strong>annual salary</strong>, the result will be if this person can buy a product on which he saw it on a social media. </p>
</li>
</ul>
<p>The app used Two ML algorithms associated with routes :</p>
<ol>
<li><strong>KMEANS</strong> : <a target="_blank" href="https://towardsdatascience.com/understanding-k-means-clustering-in-machine-learning-6a6e67336aa1?gi=e0cf29a1474f">Learn more</a></li>
<li><strong>Liniar SVC</strong>: <a target="_blank" href="https://pythonprogramming.net/linear-svc-example-scikit-learn-svm-python/#:~:text=The%20objective%20of%20a%20Linear,the%20%22predicted%22%20class%20is."> learn more</a></li>
</ol>
<p>Here is the code : </p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> sklearn.feature_extraction.text <span class="hljs-keyword">import</span> TfidfVectorizer
<span class="hljs-keyword">from</span> sklearn.cluster <span class="hljs-keyword">import</span> KMeans
<span class="hljs-keyword">from</span> sklearn.metrics <span class="hljs-keyword">import</span> adjusted_rand_score
<span class="hljs-keyword">import</span> json
<span class="hljs-keyword">import</span> os
<span class="hljs-comment"># Importing the libraries</span>
<span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np
<span class="hljs-keyword">import</span> pandas <span class="hljs-keyword">as</span> pd
<span class="hljs-keyword">from</span> flask <span class="hljs-keyword">import</span> Flask,jsonify,request,render_template
app = Flask(__name__)


<span class="hljs-meta">@app.route('/')</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">main_page</span>():</span>
    <span class="hljs-keyword">return</span> render_template(<span class="hljs-string">'index.html'</span>)

<span class="hljs-meta">@app.route('/kmeans', methods=['POST'])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">kmeans_pred</span>():</span>
    posted_data = request.get_json()
    true_k = posted_data[<span class="hljs-string">'num_cluster'</span>]
    documents = posted_data[<span class="hljs-string">'data'</span>]
    feeds = dict()
    vectorizer = TfidfVectorizer(stop_words=<span class="hljs-string">'english'</span>)
    X = vectorizer.fit_transform(documents.split(<span class="hljs-string">'.'</span>))
    model = KMeans(n_clusters=true_k, init=<span class="hljs-string">'k-means++'</span>, max_iter=<span class="hljs-number">100</span>, n_init=<span class="hljs-number">1</span>)
    model.fit(X)
    order_centroids = model.cluster_centers_.argsort()[:, ::<span class="hljs-number">-1</span>]
    terms = vectorizer.get_feature_names()
    print(terms)
    <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(true_k):
        feeds.update({<span class="hljs-string">"cluster_"</span>+str(i)+<span class="hljs-string">""</span>: [terms[ind] <span class="hljs-keyword">for</span> ind <span class="hljs-keyword">in</span> order_centroids[i, :<span class="hljs-number">10</span>]]})
    <span class="hljs-keyword">return</span> jsonify(feeds)

<span class="hljs-meta">@app.route("/ads", methods=['POST'])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">purshase</span>():</span>
    posted_data = request.get_json()
    age = int(posted_data[<span class="hljs-string">'age'</span>])
    salary = int(posted_data[<span class="hljs-string">'salary'</span>])
    new_data = [[age,salary]]
    <span class="hljs-comment"># Importing the dataset</span>
    dataset = pd.read_csv(<span class="hljs-string">'Social_Network_Ads.csv'</span>)
    X = dataset.iloc[:, [<span class="hljs-number">2</span>, <span class="hljs-number">3</span>]].values
    y = dataset.iloc[:, <span class="hljs-number">4</span>].values
    <span class="hljs-comment"># Splitting the dataset into the Training set and Test set</span>
    <span class="hljs-keyword">from</span> sklearn.model_selection <span class="hljs-keyword">import</span> train_test_split
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = <span class="hljs-number">0.25</span>, random_state = <span class="hljs-number">0</span>)
    <span class="hljs-comment"># Feature Scaling</span>
    <span class="hljs-keyword">from</span> sklearn.preprocessing <span class="hljs-keyword">import</span> StandardScaler
    sc = StandardScaler()
    X_train = sc.fit_transform(X_train)
    X_test = sc.transform(X_test)
    new_data_pred = sc.transform(new_data)
    <span class="hljs-comment"># Fitting SVM to the Training set</span>
    <span class="hljs-keyword">from</span> sklearn.svm <span class="hljs-keyword">import</span> SVC
    classifier = SVC(kernel = <span class="hljs-string">'linear'</span>, random_state = <span class="hljs-number">0</span>)
    classifier.fit(X_train, y_train)
    <span class="hljs-comment"># Predicting the Test set results</span>
    y_pred = classifier.predict(new_data_pred)
    response = {
        <span class="hljs-string">"result"</span> : str(y_pred)
    }
    <span class="hljs-keyword">return</span> jsonify(response)

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">'__main__'</span>:
    app.run(host=<span class="hljs-string">"0.0.0.0"</span>, port=os.environ.get(<span class="hljs-string">'PORT'</span>))
</code></pre>
<p>I will not explain the code, it's not the aim of this tutorial. But take a look a the last line :</p>
<pre><code class="lang-python">    app.run(host=<span class="hljs-string">"0.0.0.0"</span>, port=os.environ.get(<span class="hljs-string">'PORT'</span>))
</code></pre>
<p>The app takes an environment variable called <strong>PORT</strong> ... but Why?</p>
<p>When you log in to your Heroku pannel you will create a simple server with a custom name, that name will be used later to access the app via the internet, in-depth it is a subdomain linked to your app, it's much like an nginx reverse proxy that will point the subdomain to you deployed app, How? </p>
<p>Simply, When you hit the subdomain, Heroku will route your request to that port and return a response. If you choose your own port, your app won't work, because he doesn't know where to access the container.</p>
<h1 id="ship-the-app">Ship the app</h1>
<p>It's time to ship the app into an isolated container to be shippable on any Server!</p>
<pre><code class="lang-dockerfile">FROM frolvlad/alpine-python-machinelearning
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
EXPOSE 3000
ENV ENVIRONMENT dev
COPY . /app
CMD python main.py
</code></pre>
<p>This image <code>frolvlad/alpine-python-machinelearning</code> is ready to go image that contains what we need like the <code>sklearn</code> so we don't install it as a dependency, then we set our work directory to <code>/app</code> and copy the <code>requirements.txt</code> file to be used by the <code>pip</code> command to install <code>Flask</code>. By default, the app will run on port <code>3000</code> if we set the port on the <code>app.run(host='',port='')</code>. besides we need to copy the rest of the app code and run it with <code>python main.py</code>.   </p>
<h1 id="test-it-locally">Test it locally</h1>
<p>To test locally follow these steps : </p>
<pre><code class="lang-python"><span class="hljs-comment"># Clone the project </span>
git clone https://gitlab.com/hatemBT/REST-API-KMEANS
<span class="hljs-comment"># Run it locally </span>
pip install -r requirements.txt
<span class="hljs-comment"># Run the code </span>
python main.py
<span class="hljs-comment">#To build the docker image (change the  port=os.environ.get('PORT') =&gt; port=3000 (you can choose yor custom port))</span>
docker build -t kmeans_app . 
<span class="hljs-comment">#Run with docker </span>
docker run -p <span class="hljs-number">3000</span>:<span class="hljs-number">3000</span> kmeans_app
</code></pre>
<p>Make sure to install python3 and docker engine.</p>
<h1 id="prepare-your-server">Prepare your Server</h1>
<p>Log in to your Heroku account and then create a new app and name it <code>hello-devops-python</code> and choose the region from <code>US</code> or <code>Europe</code>, choose the nearest to you, hit create!</p>
<p>Notice that your app URL will be <code>hello-devops-python.herokuapp.com</code>.</p>
<p>We need the API key now, go to settings and grab that key. </p>
<p>We need to create a special file called <code>.netrc</code> that will allow us to connect to Heroku <code>API</code> and it's <code>Git</code> :</p>
<pre><code class="lang-python">machine api.heroku.com
  login &lt;mail&gt;
  password &lt;apiKey&gt;
machine git.heroku.com
  login &lt;mail&gt;
  password &lt;apiKey&gt;
</code></pre>
<h1 id="gitlab-pipeline">Gitlab pipeline</h1>
<p><strong>Make sure to have a GitLab account and a repository</strong></p>
<p>We need to put the apiKey and the Heroku registry URL in GitLab as secrets.
go to  <code>settings-&gt;CI/CD-&gt;variables</code> and add <code>HEROKU_APP=registry.heroku.com/hello-devops-python/web</code>, note that <code>web</code> is the container name. and <code>HEROKU_TOKEN</code> that hols the API key.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1610477581881/uIQKyhTEy.png" alt="Screenshot_2020-11-28 CI CD Settings · CI CD · hatem ben tayeb REST-API-KMEANS.png" /></p>
<p>The pipeline has a single-stage <code>development</code> that will deploy the container to the "hello-devops-python" app. Let's write the CI/CD configuration (.gitlab-ci.yml) : </p>
<pre><code class="lang-yaml"><span class="hljs-attr">image :</span> <span class="hljs-string">docker:latest</span>
<span class="hljs-attr">services:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">docker:dind</span>
<span class="hljs-attr">variables:</span>
    <span class="hljs-attr">DOCKER_DRIVER:</span> <span class="hljs-string">overlay</span>
<span class="hljs-attr">stages:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">development</span>
<span class="hljs-attr">devloppement:</span>
    <span class="hljs-attr">stage :</span> <span class="hljs-string">development</span>
    <span class="hljs-attr">script:</span>
         <span class="hljs-bullet">-</span> <span class="hljs-string">docker</span> <span class="hljs-string">build</span> <span class="hljs-string">-t</span> <span class="hljs-string">$HEROKU_APP</span> <span class="hljs-string">.</span>
         <span class="hljs-bullet">-</span> <span class="hljs-string">docker</span> <span class="hljs-string">login</span> <span class="hljs-string">--username=_</span> <span class="hljs-string">-p</span> <span class="hljs-string">$HEROKU_TOKEN</span> <span class="hljs-string">registry.heroku.com</span>
         <span class="hljs-bullet">-</span> <span class="hljs-string">docker</span> <span class="hljs-string">push</span> <span class="hljs-string">$HEROKU_APP</span>
         <span class="hljs-bullet">-</span> <span class="hljs-string">docker</span> <span class="hljs-string">run</span>  <span class="hljs-string">--rm</span>  <span class="hljs-string">-e</span> <span class="hljs-string">HEROKU_API_KEY=$HEROKU_TOKEN</span> <span class="hljs-string">wingrunr21/alpine-heroku-cli</span> <span class="hljs-string">container:release</span> <span class="hljs-string">web</span> <span class="hljs-string">--app</span> <span class="hljs-string">hello-devops-python</span>
</code></pre>
<p>Simply this pipeline follows these steps :</p>
<ul>
<li><strong>Build the container</strong>: <code>docker build -t $HEROKU_APP .</code></li>
<li><strong>login to Heroku registry</strong>: <code>docker login --username=_ -p $HEROKU_TOKEN registry.heroku.com</code></li>
<li><strong>Push the container</strong>: <code>docker push $HEROKU_APP</code></li>
<li><strong>Release the container</strong>: <code>docker run  --rm  -e HEROKU_API_KEY=$HEROKU_TOKEN wingrunr21/alpine-heroku-cli container:release web --app hello-devops-python</code>, we used a temporary docker image that contains the <code>heroku-cli</code> pre-installed, to deploy the <code>web</code> container to <code>hello-devops-python</code>.</li>
</ul>
<p><strong>Push the code</strong>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1610479086446/ZClr9_XxO.gif" alt="giphy.gif" />
<strong>Wait unit Green</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1610479158601/iCve4rXPC.png" alt="Screenshot_2020-11-28 Pipelines · hatem ben tayeb REST-API-KMEANS.png" /></p>
<h1 id="test-the-app">Test The App</h1>
<p>You can access your app now via the browser, let's test the app API :</p>
<p>Test the <strong>/kmeans</strong> route :</p>
<pre><code class="lang-bash">curl -X POST -H <span class="hljs-string">"Content-Type: application/json"</span> <span class="hljs-string">"http://hello-devops-python.herokuapp.com/kmeans"</span> -d @data.json | jq <span class="hljs-string">"."</span>
</code></pre>
<p>Example data : </p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"num_cluster"</span>:<span class="hljs-number">2</span>,
    <span class="hljs-attr">"data"</span>:<span class="hljs-string">"
The brothers wanted to build their own kingdom where people could live without fear.
 They collected a band of young men and trained them in warfare
 They lived in a forest hideout on the banks of the river Tungabhadra in South India.
One day, the brothers were out on a hunt. Ferocious dogs accompanied them.
 They crossed the river and rode on 
A couple of frightened rabbits ran out of the bushes
The dogs gave them chase with the two brothers closely behind on their horses.
"</span>
}
</code></pre>
<p>Output: </p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"cluster_0"</span>: [
    <span class="hljs-string">"accompanied"</span>,
    <span class="hljs-string">"ferocious"</span>,
    <span class="hljs-string">"dogs"</span>,
    <span class="hljs-string">"gave"</span>,
    <span class="hljs-string">"forest"</span>,
    <span class="hljs-string">"fear"</span>,
    <span class="hljs-string">"day"</span>,
    <span class="hljs-string">"crossed"</span>,
    <span class="hljs-string">"couple"</span>,
    <span class="hljs-string">"collected"</span>
  ],
  <span class="hljs-attr">"cluster_1"</span>: [
    <span class="hljs-string">"brothers"</span>,
    <span class="hljs-string">"hunt"</span>,
    <span class="hljs-string">"day"</span>,
    <span class="hljs-string">"river"</span>,
    <span class="hljs-string">"wanted"</span>,
    <span class="hljs-string">"fear"</span>,
    <span class="hljs-string">"people"</span>,
    <span class="hljs-string">"build"</span>,
    <span class="hljs-string">"live"</span>,
    <span class="hljs-string">"kingdom"</span>
  ]
}
</code></pre>
<p>Test the <strong>/ads</strong> route :</p>
<pre><code class="lang-bash">curl -X POST -H <span class="hljs-string">"Content-Type: application/json"</span> <span class="hljs-string">"http://hello-devops-python.herokuapp.com/ads"</span> -d <span class="hljs-string">"{\"age\":80,\"salary\":190000}"</span> | jq <span class="hljs-string">"."</span>
</code></pre>
<p>Output : </p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"result"</span>: <span class="hljs-string">"[1]"</span>
}
</code></pre>
<p>The presentation version :</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://slides.com/hatemtayeb/deck-a805ec">https://slides.com/hatemtayeb/deck-a805ec</a></div>
]]></content:encoded></item><item><title><![CDATA[Hiding my nodejs application code within a docker container]]></title><description><![CDATA[Building binaries on compiled languages like Rust and Go as Static or dynamic linking makes a huge step on code security, I mean no one can access your code, reading it !, but in many use cases they can by doing some complicated reverse engineering m...]]></description><link>https://blog.hatembentayeb.dev/hiding-my-nodejs-application-code-within-a-docker-container</link><guid isPermaLink="true">https://blog.hatembentayeb.dev/hiding-my-nodejs-application-code-within-a-docker-container</guid><category><![CDATA[Docker]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[Security]]></category><category><![CDATA[REST API]]></category><category><![CDATA[MongoDB]]></category><dc:creator><![CDATA[hatem ben tayeb]]></dc:creator><pubDate>Mon, 11 Jan 2021 02:38:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1610321735529/QKujkU3Kz.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>Building binaries on compiled languages like Rust and Go as Static or dynamic linking makes a huge step on code security, I mean no one can access your code, reading it !, but in many use cases they can by doing some complicated reverse engineering methods ... actually it's hard! it becomes harder when you built your app as a static linking and bundle all the shit 💩 together. Nodejs applications can be read it easily ... all your source code is accessible, In this post, we will hide our node app and "<strong>COMPILE</strong>" it! seems awesome right! let's go folks  😇 !</p>
</blockquote>
<h1 id="get-the-app">Get the app</h1>
<p>You need to have nodejs installed to test the app locally, I tested the app on <code>node 12.2.0</code>. You might have some problems with the database so make sure to use the appropriate <code>mongoose</code> version that is compatible with <code>MongoDB</code>, I have mongoose <code>5.10.10</code> that is compatible with mongo <code>4.4</code>. Let's run the app 🤓 !</p>
<h3 id="clone-the-code">Clone the code</h3>
<p>Make sure you have Git installed!</p>
<pre><code>git <span class="hljs-keyword">clone</span> https:<span class="hljs-comment">//github.com/hatembentayeb/node-rest-api-mongo.git </span>
cd node-rest-api-mongo
npm install
</code></pre><h3 id="run-a-mongo-instance">Run a mongo instance</h3>
<p>make sure you have docker installed, otherwise install mongo directly.</p>
<p><code>docker run -p 27017:27017  --name mymongo -d mongo:latest</code></p>
<h3 id="modify-the-env-file">Modify the .env file</h3>
<p>On the mongo uri we have the ip of the mongo container, you can use <code>localhost:27017</code> instead, but we need the IP when we run the app inside the container.</p>
<pre><code><span class="hljs-attr">PORT</span>=<span class="hljs-number">3000</span>
<span class="hljs-attr">MONGODB_URI</span>=mongodb://<span class="hljs-number">172.17</span>.<span class="hljs-number">0.3</span>:<span class="hljs-number">27017</span>
<span class="hljs-attr">DB_NAME</span>=hatem
</code></pre><h3 id="run-the-app">Run the app</h3>
<p>You can run it with <code>node app.js</code> </p>
<p><code>npm start</code></p>
<h3 id="insert-data">Insert data</h3>
<p>Using curl with <code>POST</code> request to send data inside the <code>products.json</code>. the <code>jq</code> command is just for formatting JSON.</p>
<pre><code><span class="hljs-attribute">curl</span>  -X POST -H <span class="hljs-string">"Content-Type: application/json"</span> <span class="hljs-string">"http://localhost:3000/products"</span> -d <span class="hljs-variable">@products</span>.json | jq
</code></pre><h3 id="get-data">Get data</h3>
<p>Simple <code>GET</code> request to get all requested data</p>
<pre><code><span class="hljs-attribute">curl</span> http://localhost:3000/products | jq
</code></pre><h1 id="why-we-need-a-binary">Why we need a binary</h1>
<p>This approach! i mean building binary can save us especially to get rid of the <code>node_modules</code> inside our container! we will save space and enhance the image size! besides we don't need any base node image like <code>FROM node: latest</code> because it's just a binary that needs a shell to run, also you have to make a choice of your build platform or architecture, you can find the full list of the available architectures for <code>nexe</code> on this link <a target="_blank" href="https://github.com/nexe/nexe/releases">full list</a>. </p>
<p>In this tutorial, we need a minimal docker image ... like  <code>5MB</code> of size ! , that's awesome ! yes, it's is the <code>alpine</code> image, the smallest one! now we can deploy a docker image with a low size! it will be extremely fast believe me, if you have a small server with limited os storage or an on-premise docker registry with limited storage it will be wonderful to store images with small size  !! </p>
<p>If you try to build the project with <code>nexe</code> and then use it on a docker container based on alpine, it will work !, only in your dreams 🤣. To be able to run it on alpine we need to set the right architecture platform before the build. </p>
<p>In terms of security, you are almost secure they can't get to your source code! it's a binary! dude, but they can access your container if they succeeded to create shell access with the <code>RCE (remote code execution) attack</code> ... they can do it by attacking your application and exploit it,  so they are on your container now! they can't read your code but they can crash your container and access your host and here is the disaster 😳 ! so I will try to limit the privileges on the container by disabling the root access and create a non-root user and remove some of the default Linux commands like <code>ls, cat and mv</code> 😈. </p>
<p><strong>Anyways, let's start building binaries 😋</strong></p>
<h1 id="lets-build-it">Let's build it</h1>
<p>In order to build the binary, we need a special package called <code>nexe</code> with over then <code>9k</code> stars on GitHub 🧐 !!</p>
<p>To install it run this command <code>npm install -g nexe</code> and check the package version <code>nexe -v</code>, I have the <code>4.0.0-beta.16</code> version. </p>
<p>To build the application just run this command! :</p>
<pre><code><span class="hljs-attribute">next</span> app.js -t alpine-x<span class="hljs-number">86</span>-<span class="hljs-number">12</span>.<span class="hljs-number">2</span>.<span class="hljs-number">0</span> -o mybinary
</code></pre><p>let's breakdown the nexe options : </p>
<ul>
<li><code>app.js</code>: is your application entry point</li>
<li><code>alpine-x86-12.2.0</code>: is the node version that is compatible with the alpine architecture platform with is <code>x86</code> </li>
<li><code>mybinary</code>: is your binary output file that we will copy to the container.</li>
</ul>
<p>In some cases you need to bundle some <code>HTML</code> files to your binary or any some directories, you can do that by adding the <code>--recursive/-r</code> option and specify the path like this :</p>
<pre><code><span class="hljs-selector-tag">nexe</span> <span class="hljs-selector-tag">app</span><span class="hljs-selector-class">.js</span> <span class="hljs-selector-tag">-t</span> <span class="hljs-selector-tag">alpine-x86-12</span><span class="hljs-selector-class">.2</span><span class="hljs-selector-class">.0</span> <span class="hljs-selector-tag">-r</span> <span class="hljs-selector-tag">static</span><span class="hljs-comment">/**/</span>*<span class="hljs-selector-class">.html</span> <span class="hljs-selector-tag">-o</span> <span class="hljs-selector-tag">mybinary</span>
</code></pre><p>Alright let's run the binary : </p>
<pre><code>$ ./mybinary 

Server started <span class="hljs-keyword">on</span> port <span class="hljs-number">3000.</span>..
Mongoose connected to db...
Mongodb connected....
</code></pre><p>And here we go! it's much like a command! try to move it to another location on your file system and run it again! awesome it works smoothly, congrats! you have a standalone binary for your node app  😍 !</p>
<h1 id="move-it-to-docker">Move it to docker</h1>
<p>let's run the app with docker, we need a dockerfile to build the image, here is an example: </p>
<pre><code><span class="hljs-keyword">FROM</span> alpine
WORKDIR app
RUN adduser <span class="hljs-comment">--disabled-password btx</span>
RUN rm -f /bin/cat /bin/ls /bin/mv /bin/find /bin/cd 
<span class="hljs-keyword">COPY</span> hatem .
<span class="hljs-keyword">COPY</span> .env .
EXPOSE <span class="hljs-number">3000</span>
RUN chown -R btx:btx /app
<span class="hljs-keyword">USER</span> btx
CMD  ["./mybinary"]
</code></pre><p>We will base the container on a lightweight docker image: <strong>Alpine</strong> it's about 5MB in size! then we define our working directory <strong>/app</strong> to be the default directory for the rest of all commands and when you access the container via <strong>exec</strong>. Adding a non-root user called <strong>btx</strong> without a password and remove some of the basic Linux commands like <strong>ls</strong> , <strong> cat</strong> , <strong>mv</strong> , <strong>find</strong> and <strong>cd</strong>. Now no need to copy the whole project 🥲, just that tiny binary! and that's it. We know that the app depends on a <strong>.env</strong> file so we copy it inside the container. </p>
<p>Exposing the container port is dependent on the port number on the <strong>.env</strong> file. Now all artifacts are in the right place, so now let's change the directory ownership to the <strong>btx</strong> user and activate that user to be the default one when we access the container via exec. The final command is to start the app as a single process on the container.</p>
<p>Time to <strong>cook</strong>  😊</p>
<p>Build the docker image : </p>
<pre><code>$ docker build -t node-api-crud:v1 . <span class="hljs-comment">--no-cache</span>
</code></pre><p>Run the docker image :</p>
<pre><code><span class="hljs-string">$</span> <span class="hljs-string">docker</span> <span class="hljs-string">run</span> <span class="hljs-string">-p</span> <span class="hljs-number">3000</span><span class="hljs-string">:3000</span> <span class="hljs-string">--name</span> <span class="hljs-string">node-rest</span>  <span class="hljs-string">node-api-crud:v1</span>
<span class="hljs-string">Server</span> <span class="hljs-string">started</span> <span class="hljs-string">on</span> <span class="hljs-string">port</span> <span class="hljs-number">3000</span><span class="hljs-string">...</span>
<span class="hljs-string">Mongoose</span> <span class="hljs-string">connected</span> <span class="hljs-string">to</span> <span class="hljs-string">db...</span>
<span class="hljs-string">Mongodb</span> <span class="hljs-string">connected....</span>
</code></pre><p>We can now test the app and start inserting data and getting them via curl via <strong>localhost:3000</strong> </p>
<h1 id="final-thoughts">Final thoughts</h1>
<p>To ensure more security test try to use : </p>
<ul>
<li><strong>npm audit fix</strong> to fix any dependency vulnerability.</li>
<li><strong>Anshore</strong> for scanning the docker image and check if there is any critical CVE on the system packages.</li>
<li><strong>Trusted images</strong> from official docker hub repos, or make your own.</li>
<li><strong>Sonarqube</strong> to make some <strong>SAST</strong> and discover potential security issues on your code.</li>
</ul>
<h2 id="andnbspandnbspandnbspandnbspandnbspandnbspandnbspandnbspandnbspyou-build-it-you-run-it-you-secure-it">         You build it, you run it, you secure it</h2>
]]></content:encoded></item><item><title><![CDATA[Mailcow: Setting up a full featured self hosted mail server]]></title><description><![CDATA[Setting up a full-featured mail server is an actual PAIN for a system admin, you have to interconnect many software pieces and a lot of testing especially security and spam if you plan to use it for your company ... In this Post, I will take you in m...]]></description><link>https://blog.hatembentayeb.dev/mailcow-setting-up-a-full-featured-self-hosted-mail-server</link><guid isPermaLink="true">https://blog.hatembentayeb.dev/mailcow-setting-up-a-full-featured-self-hosted-mail-server</guid><category><![CDATA[email]]></category><category><![CDATA[Linux]]></category><category><![CDATA[business]]></category><category><![CDATA[Open Source]]></category><category><![CDATA[Emails]]></category><dc:creator><![CDATA[hatem ben tayeb]]></dc:creator><pubDate>Fri, 08 Jan 2021 17:25:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1610126555088/Ko4_WHzHH.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>Setting up a full-featured mail server is an actual <strong>PAIN</strong> for a system admin, you have to interconnect many software pieces and a lot of testing especially security and spam if you plan to use it for your company ... In this Post, I will take you in my journey of setting up a  full-featured mail server, I was searching a lot on the internet and I didn't get a sweetly answer to move on .. but finally I successfully made it! .. It works with multiple domains, secure, and no spam problems!</p>
</blockquote>
<h1 id="for-the-reader">For the reader</h1>
<p><strong>I assume you have knowledge about how email works, Mail DNS records, Linux, docker, and SSL of course. This guide is not for beginners</strong></p>
<p>If you don't have enough knowledge, take a seat and take a look here : </p>
<table>
<thead>
<tr>
<td>Term</td><td>Definition</td></tr>
</thead>
<tbody>
<tr>
<td><strong>Mail server</strong></td><td>Is a computer system that sends and receives email <a target="_blank" href="https://techterms.com/definition/mail_server"><em>source</em></a></td></tr>
<tr>
<td><strong>MX</strong></td><td>Used to tell the world which mail servers accept incoming mail for your domain <a target="_blank" href="https://help.dreamhost.com/hc/en-us/articles/215032408-What-is-an-MX-record-"><em>source</em></a></td></tr>
<tr>
<td><strong>CNAME</strong></td><td>Used to alias one name to another, CNAME stands for Canonical Name. <a target="_blank" href="https://support.dnsimple.com/articles/cname-record/"><em>source</em></a></td></tr>
<tr>
<td><strong>DKIM</strong></td><td>Is an email authentication technique that allows the receiver to check that an email was indeed sent and authorized by the owner of that domain <a target="_blank" href="https://www.dmarcanalyzer.com/dkim/"><em>source</em></a></td></tr>
<tr>
<td><strong>SPF</strong></td><td>Is an email-authentication technique that is used to prevent spammers from sending messages on behalf of your domain  <a target="_blank" href="https://www.dmarcanalyzer.com/spf/"><em>source</em></a></td></tr>
<tr>
<td><strong>DMARC</strong></td><td>is an email validation system designed to protect your company’s email domain from being used for email spoofing <a target="_blank" href="https://www.dmarcanalyzer.com/dmarc/"><em>source</em></a></td></tr>
</tbody>
</table>
<h1 id="mailcow">Mailcow</h1>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1610109314178/O0QJMagRl.png" alt="256.png" />
Mailcow is a free, open-source software project. A Mailcow server is a collection of Docker containers running different mail server applications, SOGo, Postfix, Dovecot, etc. Mailcow provides a modern and easy to use the web interface to create and manage email accounts. You can visit the <a target="_blank" href="https://mailcow.github.io/mailcow-dockerized-docs/">official documentation</a></p>
<p>I ill not make a comparison about different opensource self-hosted mail servers, rather then that I will give you a great repo that includes awesome software marked as self-hosted, you can check the Email  section  <a target="_blank" href="https://github.com/awesome-selfhosted/awesome-selfhosted#complete-solutions">come here</a></p>
<p>If you want some thoughts about mailcow from real users check these Reddit discussions: <a target="_blank" href="https://www.reddit.com/r/selfhosted/comments/hdgau0/whats_the_current_take_on_selfhosting_mail/">come here</a> and <a target="_blank" href="https://www.reddit.com/r/selfhosted/comments/4ct285/iredmail_vs_mailcow_vs_mailinabox/">here</a></p>
<h1 id="server-requirements">Server Requirements</h1>
<p>The recommended OS to run mailcow is <code>ubuntu 18.04</code>, also don't use cento7 packages on cento8  because the maintainers said : </p>
<blockquote>
<p>Do not use CentOS 8 with Centos 7 Docker packages. You may create an open relay.</p>
</blockquote>
<p>For the resources, I bought a VPS from OVH cloud provider with : </p>
<ul>
<li>2 VCPU </li>
<li>4 GB of RAM </li>
<li>80 GB storage </li>
</ul>
<p>Docker installed : </p>
<pre><code class="lang-bash">curl -sSL https://get.docker.com/ | CHANNEL=stable sh
systemctl <span class="hljs-built_in">enable</span> docker.service
systemctl start docker.service
</code></pre>
<p>Docker-compose installed : </p>
<pre><code class="lang-bash">curl -L https://github.com/docker/compose/releases/download/$(curl -Ls https://www.servercow.de/docker-compose/latest.php)/docker-compose-$(uname -s)-$(uname -m) &gt; /usr/<span class="hljs-built_in">local</span>/bin/docker-compose
chmod +x /usr/<span class="hljs-built_in">local</span>/bin/docker-compose
</code></pre>
<p>If you have a firewall, you should allow those ports : </p>
<pre><code class="lang-bash">netstat -tulpn | grep -E -w <span class="hljs-string">'25|80|110|143|443|465|587|993|995|4190'</span>
</code></pre>
<blockquote>
<p><strong>Note</strong>: you must have a clean server means no other applications or a reverse proxy because mailcow has everything in place.</p>
</blockquote>
<p>Okay, now everything is good! but there is another thing to verify which is our IP !!! 
we have to test it if it is <strong>blacklisted</strong>, if yes it's a huge problem ... we can't proceed anymore because your mails will not be delivered, it's all about your host <strong>reputation</strong>!. </p>
<p>We can check it with an online service called <strong>MX toolbox</strong> by visiting this URL <a target="_blank" href="https://mxtoolbox.com/SuperTool.aspx">mxtoolbox</a>.</p>
<h1 id="basic-dns-configuration">Basic DNS configuration</h1>
<p>Before we proceed we have to get a domain name from any provider, I got one from <code>OVH</code>, and I delegated it to <code>AZURE DNS SERVICE</code>, it doesn't matter actually, we will have the same configuration in any provider.</p>
<p>I pretend that i have : </p>
<ul>
<li><strong>Root Domain name</strong> : mymailserver.com</li>
<li><strong>Server IP address</strong> : 1.2.3.4 </li>
</ul>
<p><strong>Let's get started !</strong></p>
<h2 id="create-an-a-record">Create an <strong>A</strong> record</h2>
<p>In your provider DNS panel add an <code>A</code> record like this </p>
<ul>
<li><strong>Name</strong> : mail </li>
<li><strong>Type</strong> : A</li>
<li><strong>TTL</strong> : default </li>
<li><strong>Value</strong> : 1.2.3.4</li>
</ul>
<p>You can confirm with the <code>dig</code> command : </p>
<pre><code class="lang-bash"> dig mail.mymailserver.com +noall +answer
</code></pre>
<h2 id="create-the-cname-records">Create the <strong>CNAME</strong> records</h2>
<p>In your provider DNS panel add a <code>CNAME</code> record for the <code>autoconfig</code> like this </p>
<ul>
<li><strong>Name</strong> : autoconfig </li>
<li><strong>Type</strong> : CNAME</li>
<li><strong>TTL</strong> : default </li>
<li><strong>Alias</strong> : mail.mymailserver.com</li>
</ul>
<p>Test it with : <code>dig autoconfig.mymailserver.com +noall +answer</code></p>
<p>Add another <code>CNAME</code> record for the <code>autodiscover</code> like this :</p>
<ul>
<li><strong>Name</strong> : autodiscover </li>
<li><strong>Type</strong> : CNAME</li>
<li><strong>TTL</strong> : default </li>
<li><strong>Alias</strong> : mail.mymailserver.com</li>
</ul>
<p>Test it with : <code>dig autodiscover.mymailserver.com +noall +answer</code></p>
<h2 id="create-an-mx-record">Create an MX record</h2>
<p>In your root domain add an MX domain to point to your mail server domain : </p>
<ul>
<li><strong>Name</strong> : @/empty  </li>
<li><strong>Type</strong> : MX</li>
<li><strong>TTL</strong> : default </li>
<li><strong>Prefenerence/periorty</strong> : 10 or 0 </li>
<li><strong>Mail Exchange</strong> : mail.mymailserver.com</li>
</ul>
<h2 id="rdns-configuration">rDNS configuration</h2>
<p>Get more information about <code>rDNS</code> <a target="_blank" href="https://help.returnpath.com/hc/en-us/articles/360019088671-How-to-set-up-reverse-DNS-zone-and-PTR-records-">here</a> </p>
<p>You can configure your rDNS on the provider of your <code>server</code> and change the generated <code>domain name</code> to your mail domain <code>mail.mymailserver.com</code></p>
<p>and test it with <code>dig -x 1.2.3.4 +noall +answer</code>, should get <code>mail.mymailserver.com</code> as responce.</p>
<h1 id="security-dns-records">Security DNS Records</h1>
<p>Authenticate your mail server and protect it from <a target="_blank" href="https://www.serversmtp.com/what-is-dkim/">Fake identities</a>  and  <a target="_blank" href="https://www.barracuda.com/glossary/domain-spoofing">Domain spoofing attacks</a> we have to set up those records </p>
<h2 id="spf-record">SPF record</h2>
<p>In your root domain add a <code>TXT</code> record like this : </p>
<ul>
<li><strong>Name</strong> : @/empty  </li>
<li><strong>Type</strong> : TXT</li>
<li><strong>TTL</strong> : default </li>
<li><strong>value</strong> : v=spf1 ip4:1.2.3.4 -all</li>
</ul>
<p>Test it with <code>dig mymailserver.com TXT</code></p>
<h2 id="dkim-record">DKIM record</h2>
<p>Setting up the dkim record needs a public key to be inserted in the record here but we can't now because we have to install mailcow and get the public key given by our mail server, we will leave it empty for now. </p>
<ul>
<li><strong>Name</strong> : dkim._domainkey</li>
<li><strong>Type</strong> : TXT</li>
<li><strong>TTL</strong> : default </li>
<li><strong>value</strong> : v=DKIM1;k=rsa;t=s;s=email;<strong>p=</strong></li>
</ul>
<h2 id="dmarc-record">DMARC record</h2>
<p>The best thing you can do for dmarc is to make a free account on <a target="_blank" href="https://dmarcian.com/">dmarcian</a>, it will give you a record like this : </p>
<pre><code class="lang-bash">v=DMARC1; p=reject; rua=mailto:&lt;dmarc email1&gt;; ruf=&lt;dmarc email2&gt;
</code></pre>
<p>Just add it to your domain DNS pannel like this : </p>
<ul>
<li><strong>Name</strong> : _dmarc</li>
<li><strong>TTL</strong> : default </li>
<li><strong>value</strong> : v=DMARC1; p=reject; rua=mailto: dmarc email1 ; ruf=dmarc email2</li>
</ul>
<p>The test is with: <code>dig _dmarc.mymailserver.com TXT</code></p>
<p><strong>That's it for the DNS configurations, all records are in place except the dkim value we ill add it later</strong></p>
<h1 id="install-mailcow">Install mailcow</h1>
<p>You need <code>git</code> installed to clone the repo in your server : </p>
<pre><code class="lang-bash"> git <span class="hljs-built_in">clone</span> https://github.com/mailcow/mailcow-dockerized
 <span class="hljs-built_in">cd</span> mailcow-dockerized
</code></pre>
<p>You in the repo now, so run the script <code>./generate_config.sh</code> to generate your config, for the hostname add <code>mail.mymailserver.com</code>, it will request a certificate from <code>Let'svEncrypt</code> automatically. </p>
<p>To run your mail server just use this command : </p>
<pre><code class="lang-bash">docker-compose pull
docker-compose up -d
</code></pre>
<p><strong>You need to wait sometime to download all the containers, run them and provision an SSL certificate.</strong></p>
<p>The default credentials to access are <code>admin</code> &amp; <code>moohoo</code> (you must change it with your own)</p>
<p>Verify that all containers are healthy with the <code>green icon</code> like this :</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1610120906694/XD99OD4X3.png" alt="Screenshot_2021-01-08 mailcow UI.png" /></p>
<p><strong>Well done! Let's proceed to add domains and mailboxes</strong></p>
<h1 id="configure-mailcow">Configure Mailcow</h1>
<p>Our mail server is working now but there are neither domains nor mailboxes configured. in this section, we will go step by step .. let's GO!</p>
<h2 id="add-your-domain">Add your domain</h2>
<p>Login to your mailcow admin panel :</p>
<ol>
<li>Select <code>configuration</code> on the top bar then select <code>mail setup</code></li>
<li>Click on <code>add domain</code> then add your root domain <code>mymailserver.com</code> and click on <code>Add domain and restart SoGo</code></li>
<li>Select <code>configuration</code> again then click on <code>configuration &amp; details</code></li>
<li>In the horizontal menu click <code>configuration</code> and click on <code>ARC/DKIM keys</code></li>
<li>Scroll to the bottom and fill the <code>domain/s</code> form with your domain <code>mymailserver.com</code></li>
<li>On the selector type <code>dkim</code> </li>
<li>Select <code>2048</code> on the <code>DKIM key length</code> and click <code>Add</code></li>
<li>Copy the generated public key that starts with <code>v=DKIM1;k=rsa;t=s;s=email;p= ...</code></li>
<li>Go to your DNS panel and modify the <code>TXT</code> record with the generated value</li>
<li>Wait until your DNS modification propagate  </li>
</ol>
<p>You can validate your configuration for <code>SPF</code>, <code>DKIM</code> and <code>DMARC</code> with this        <a target="_blank" href="https://dmarcian.com/">site</a>, you should get a result like this (don't forget to put <code>dkim</code> as a selector` :</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1610122571096/eV1_YdWK8.png" alt="Screenshot_2021-01-08 Free DMARC Domain Check Is Your Domain Protected - dmarcian.png" /></p>
<h2 id="create-a-mailbox-for-mymailservercom">Create a Mailbox for mymailserver.com</h2>
<p>By default, mailcow give <code>10GB</code> of storage to every domain and every mailbox under that domain has <code>3GB</code> of storage .. so feel free to modify them to your needs, </p>
<p>To create a mailbox follow those steps : </p>
<ol>
<li>Under <code>configuration</code> on the top bar click <code>mail setup</code> and select mailboxes`</li>
<li>Click <code>add mailbox</code></li>
<li>Choose a <code>username</code>, your domain (it will be loaded automatically) and then add a password and click <code>Add</code></li>
<li>On the top bar click <code>apps</code> and then <code>webmail</code>, you will be redirected to the <code>SoGo</code> UI, login with your newly created mail and password</li>
<li>on the bottom click on the green icon, you will get an interface for sending a mail </li>
<li>In your browser open another tab and go to this site: mail-tester.com and copy the mail address.</li>
<li>Return to your <code>SoGo</code> interface and send a mail with that mail, on the body make sure to add some text with a least two paragraphs.</li>
<li>Wait 10 seconds and go the mail-tester.com and click on <code>then check your score</code> </li>
</ol>
<p>And <strong>BOOM</strong> you should get this result <strong>You Can Send</strong>  : </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1610123568730/QfI2NwVUH.png" alt="Screenshot_2021-01-07 Spam Test Result.png" /></p>
<p>Feel free to send a mail to your friends and your own Gmail address, don't worry Gmail still doesn't know your mail server, so he will place it as spam, just <code>unspam</code> it! try to send it to Zoho mail for example. The test again with other tools I suggest to use : </p>
<ul>
<li><a target="_blank" href="https://mxtoolbox.com/SuperTool.aspx">Mxtoolbox SuperTool</a> and select <code>test mail server</code></li>
<li><a target="_blank" href="https://dkimvalidator.com/">Dkimvalidator</a> and send an email to the given address, make sure that all tests are passed like the <code>dmarcian.com</code> tests.</li>
</ul>
<h2 id="add-another-domain-to-mailcow">Add another domain to mailcow</h2>
<p>I assume we have a second domain: myseconddomain.com.
I will not repeat the same steps it's quite easy so let's go :</p>
<ol>
<li>Create A record: <code>mail.myseconddomain.com</code> that points to your Host <code>1.2.3.4</code> </li>
<li>Create a CNAME record: <code>autoconfig</code> that points to <code>mail.myseconddomain.com</code></li>
<li>Create a CNAME record: <code>autodiscover</code> that points to <code>mail.myseconddomain.com</code></li>
<li>Create an MX record: on the root domain add <code>10</code> as a priority and <code>mail.myseconddomain.com</code> as target </li>
<li>Create SPF record as we did earlier</li>
<li>Create a DMARC record as we did earlier </li>
<li>Create a DKIM record and at the same time add your new domain as we did earlier and copy the generated <code>DKIM key</code> to your <code>DKIM</code> record. </li>
<li>Validate your records </li>
<li>Add a mailbox under your new domain and send an email to mail-tester.com and dkimvalidator.com, you should get 10/10 sweetheart :)</li>
</ol>
<h1 id="final-thoughts">Final thoughts</h1>
<p>Don't send a lot of emails directly you will be blocked! ... so start step by step by sending 20 emails per day for the first week then try sending 80 the next week ... after 2 months you can send a lot of mails like 1000 emails, but take in mind that you need to add the <code>list-unsubscribe headers</code> to your postfix to allow users to unsubscribe against your newsletters/subscriptions. </p>
<p>please follow me on Twitter <a target="_blank" href="https://twitter.com/hatembentayeb">@hatem ben tayeb</a></p>
]]></content:encoded></item><item><title><![CDATA[Deploying a Dockerized Angular App with Github Actions]]></title><description><![CDATA[In this article we will discover the DevOps movement step by step, we will talk about the fundamental concepts then we will make a basic pipeline with GitHub actions to deploy an Angular 6 app, so let’s go.

What is DevOps?
DevOps is used to remove t...]]></description><link>https://blog.hatembentayeb.dev/deploying-a-dockerized-angular-app-with-github-actions-1</link><guid isPermaLink="true">https://blog.hatembentayeb.dev/deploying-a-dockerized-angular-app-with-github-actions-1</guid><category><![CDATA[Angular 2]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Devops]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[github-actions]]></category><dc:creator><![CDATA[hatem ben tayeb]]></dc:creator><pubDate>Mon, 04 Jan 2021 19:46:31 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1609789559730/03Lq8SJDa.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>In this article we will discover the DevOps movement step by step, we will talk about the fundamental concepts then we will make a basic pipeline with GitHub actions to deploy an Angular 6 app, so let’s go.</p>
</blockquote>
<h1 id="what-is-devops">What is DevOps?</h1>
<p>DevOps is used to remove the conflict between the developers' team and the operations team to work together. This conflict is removed by adding a set of best practices, rules, and tools. The DevOps workflow is defined with a set of steps :</p>
<p><img src="https://miro.medium.com/max/60/1*EBXc9eJ1YRFLtkNI_djaAw.png?q=20" alt="Image for post" /></p>
<img alt="Image for post" src="https://miro.medium.com/max/3964/1*EBXc9eJ1YRFLtkNI_djaAw.png" />


<h2 id="plan">Plan</h2>
<p>This is the first step, where the team defines the product goals and phases, also defining deadlines and assigning tasks to every team member, this step is the root of the whole workflow. the team uses many methodologies like scrum and agile.</p>
<h2 id="code">Code:</h2>
<p>After planning, there is the code when the team converts the ideas to code. every task must be coded and merged to the main app, here we use an <strong>SCM</strong> to organize the collaboration to make a clean code and have a full code history to make a rollback in case of failure.</p>
<h2 id="build">Build:</h2>
<p>After coding, we push the code to Github or Gitlab ( SCM ) and we make the build, usually, we use docker images for packaging. also, we can build the code to be a Linux package like deb, rpm … or even zip files, also there is a set of tests like unit tests and integration tests. this phase is critical!</p>
<h2 id="test">Test:</h2>
<p>The build was succeeded, no it’s time to deploy the build artifacts to the staging server when we apply a set of manual and automated tests ( UAT ).</p>
<h2 id="release">Release:</h2>
<p>it’s the final step for the code work, so we make a release and announce a stable version of our code that is fully functional! also, we can tag it with a version number.</p>
<h2 id="deploy">Deploy:</h2>
<p>A pre-prod or a production server is the target now, to make our app up and running</p>
<h2 id="operate">Operate:</h2>
<p>It’s all about infrastructure preparation and environment set up with some tools like terraform for IaaC, Ansible for configuration management, and security stuff configurations …</p>
<h2 id="monitor">Monitor:</h2>
<p>The performance is very important, so we install and configure some monitoring tools like ELK, Nagios, and datadog to get all information about the applications like CPU and memory usage …</p>
<h1 id="deploying-an-angular-app">Deploying an angular app</h1>
<p>In this example, we will deploy a simple angular app on two environments.</p>
<ul>
<li>On VPS ( OVH provider) as a development server.</li>
<li>on Heroku as a staging server.</li>
</ul>
<p>So you must have a VPS and a Heroku account to continue with me.</p>
<p>The application repository is here : <a target="_blank" href="https://github.com/hatembentayeb/angular-devops">Github repo</a>.</p>
<ol>
<li>Clone the project with <code>git clone https://github.com/hatembentayeb/angular-devops</code></li>
<li>run <code>npm install &amp;&amp; ng serve</code> to run the app locally</li>
</ol>
<h2 id="preparing-the-deployment-for-heroku">Preparing the deployment for Heroku</h2>
<p>Nginx is a popular and powerful web server that can be used to serve a large variety of apps based on python, angular, and react …</p>
<p>I will go through an optimization process to produce a clean and lightweight docker container with the best practices as much as I can.</p>
<h2 id="writing-the-dockerfile">Writing the Dockerfile</h2>
<p>First, we will prepare the Dockerfile to be deployed to the Heroku cloud, so there are some tricks to make it work smoothly, make sure that you have an account and simply click new to create an app :</p>
<p><img src="https://miro.medium.com/max/60/1*nti0ILLxeBTzz3GmB8ZeTA.png?q=20" alt="Image for post" /></p>
<img alt="Image for post" src="https://miro.medium.com/max/1732/1*nti0ILLxeBTzz3GmB8ZeTA.png" />

<p>create a Heroku app</p>
<p>Make sure to give a valid name for your app, then go to your <strong>account settings</strong> and get your <code>API_KEY</code> that we will use in the pipeline file:</p>
<p><img src="https://miro.medium.com/max/60/1*bO0u_UUltF_8lXxnynqq9g.png?q=20" alt="Image for post" /></p>
<img alt="Image for post" src="https://miro.medium.com/max/2698/1*bO0u_UUltF_8lXxnynqq9g.png" />

<p>getting API key</p>
<p>let’s take a look at the <code>dockerfile</code> of the app:</p>
<p>This Dockerfile is split into two stages :</p>
<ul>
<li><strong>Builder stage:</strong> The name of the stage is builder, it is a temporary docker container that produces an artifact which is the <code>dist/</code> folder created by <code>ng build --prod</code> that compiles our project to produce a single <code>HTML</code> page and some <code>*js &amp; *.css</code>. The base images that are used here are <code>trion/ng-cli</code> that contains all requirements to run an angular up and it’s accessible for public use in the <code>Docker-hub</code>, the public docker registry.
Make sure to install all app requirement packages with <code>npm ci</code>, the <code>ci</code> command is used often in the continuous integration environments because it is faster than <code>npm install.</code></li>
<li><strong>Final stage:</strong> The base image for this stage is <code>nginx:1.17.5</code> and simply we copy the <code>dist/</code> folder from the <code>builder</code> stage to the <code>/var/share/nginx/html</code> folder in the nginx container with the command <code>COPY --from=builder ...</code>
There are additional configurations required to run the app, we need to configure nginx, there is a file named <code>default.conf.template</code> that contains a basic nginx configuration so we copy it to the container under <code>/etc/nginx/conf.d/default.conf.template</code> , this file has the <strong><em>$PORT</em></strong> variable that has to be changed when building the docker image in the Heroku environment.
The <code>default.conf.template</code> :</li>
</ul>
<pre><code>server {                         
<span class="hljs-keyword">listen</span> $PORT default_server;

location / {                           
include  /etc/nginx/mime.types;                                                      root   /usr/share/nginx/html/;
<span class="hljs-keyword">index</span>  index.html index.htm;       
}                                                                       }&lt;<span class="hljs-regexp">/span&gt;</span>
</code></pre><p>Also, make sure to copy the <code>nginx.conf</code> under the <code>/etc/nginx/nginx.conf</code>, you are free to change and modify 😃, but for now I will use the default settings.
The last command is a little bit confusing so let’s break it down :</p>
<pre><code>CMD /bin/bash -c “envsubst ‘\$PORT’ &lt; <span class="hljs-regexp">/etc/</span>nginx/conf.d/<span class="hljs-keyword">default</span>.conf.template &gt; <span class="hljs-regexp">/etc/</span>nginx/conf.d/<span class="hljs-keyword">default</span>.conf” &amp;&amp; nginx -g ‘daemon off;’&lt;/span&gt;
</code></pre><p>→ <strong><em>/bin/bash -c ‘ command’:</em></strong> This command will run a Linux command with <em>the bash shell.
→</em> <strong><em>envsubst</em></strong>: It is a program that substitutes the values of environment variables, so it will replace the <strong>$PORT</strong> from the Heroku environment and replace it in the <code>default.conf.template</code> file with its value, this variable is given by Heroku and attached to your app name, then we rename the template with <code>default.conf</code> which is recognized by nginx.
→ <strong><em>nginx -g ‘daemon off;’</em></strong>: The <code>daemon off;</code> directive tells Nginx to stay in the foreground. For containers, this is useful as the best practice is for one container = one process. One server (container) has only one service.</p>
<h2 id="preparing-the-deployment-for-the-vps-on-ovh">Preparing the deployment for the VPS on OVH</h2>
<p>We will use the VPS as a development server so no need for a docker now we will use ssh for this after all make sure to have a VPS, ssh credentials, and a public IP.</p>
<p>I assume you have <code>nginx</code> installed, if not try to do it, it is simple 😙</p>
<p>In this tutorial I will be using the <strong><em>sshpass</em></strong> command, it is powerful and suitable for CI environments.
You can install it with: <code>apt-get install sshpass -y .</code></p>
<p>lets deploy the app to our server from the local machine, navigate to the repo and run <code>ng build --prod</code> , then navigate to <code>dist/my-first-app</code> folder and type this command :</p>
<pre><code>sshpass  scp -v -p &lt;password&gt;  -o stricthostkeychecking=no -r *.* root@&lt;vps-ip&gt;<span class="hljs-symbol">:/usr/share/nginx/html&lt;/span&gt;</span>
</code></pre><p>If you don’t want to hardcode the password in the command line try to set the <code>SSHPASS</code> variable with your password like this <code>export SSHPASS=" password"</code> and replace <code>-p</code> with <code>-e</code> to use the environment variable.</p>
<p>Now all things are almost done! great 😃 ! let’s prepare the <strong>pipeline</strong> in the GitHub actions which is a fast and powerful ci system provided by GitHub inc.</p>
<p>Under the project root folder create the file <code>main.yml</code> in the <code>github/wokflows</code> folder, this directory is hidden so must start with a point like this: <code>.github/workflows/main.yml</code></p>
<h2 id="preparing-the-pipeline">Preparing the pipeline</h2>
<p>let’s take a look at the pipeline steps and configurations :</p>
<p>.github/workflows/main.yml</p>
<ul>
<li><strong>Block 1</strong>: In this block, we define the workflow name and the actions that must be performed to start the build, test, and deployment. and of course, you have to specify the branch of your repo (by default <code>master</code> ).</li>
<li><strong>Block 2</strong>: The <code>jobs</code> keyword has to sub-keywords <code>build</code> and <code>steps</code>, the build define the base os for the continuous integration environment, in this case, we will use <code>ubuntu-latest</code>, also we define the <code>node-version</code> as a matrix that allows us to use multiple node versions in the list, in this case, we need only <code>12.x</code>. The steps allow us to define the workflow steps and configurations ( build, test, deploy...).</li>
<li><strong>Block 3</strong> : <code>actions/checkout@v1</code> is used to clone the app code in the ci env. this action is provided by GitHub.
Let's define a <code>cache</code> action with the name <code>cache node modules</code>, the name is up to you 😃, then we use a predefined action called <code>actions/cache@v1</code> and specify the folders that we want to cache.</li>
<li><strong>Block 4</strong>: Installing and configuring the node run-time with an action called <code>actions/node-setup@v1</code> and pass to it the desired node version that we already defined.</li>
<li><strong>Block 5</strong>: The show will begin now! let’s configure the build and the deployment to the VPS. Create two environment variables <code>SSHPASS</code> for the sshpass command and define the <code>server</code> address, make sure to define these values on the GitHub secrets under setting on the top of your repo files. Under the <code>run</code> keyword put your deployment logic. so we need the sshpass command and the angular cli to be installed, then install all required packages and build the app with the production mode <code>--prod</code>, next, navigate to the <code>dist/my-first-app</code> folder and run the sshpass command with a set of arguments to remove the older app in the server and deploy the new code.</li>
<li><strong>Block 6</strong>: Now Heroku is our target, so define also two env. variables, the Heroku registry URL and the API KEY to gain access to the registry using docker, next we need to define a special variable <code>HEROKU_API_KEY</code> that is used by Heroku cli, next, we login to the Heroku container and build the docker image then we pushed to the registry. we need to specify the target app in my case I named it <code>angulardevops</code>. After deploying the docker image we need to release it and tell the Heroku dynos to run our app on a Heroku server, using 1 server <code>web=1</code>, note that <code>web</code> is the name of the docker image that we already pushed.</li>
</ul>
<p>We are almost done! now try to make a change in the app code and push it to GitHub, the workflow will start automatically 🎉 🎉 😄 !</p>
<p><img src="https://miro.medium.com/max/60/1*O2HJGDQaLjiL4ePKNdHHWw.png?q=20" alt="Image for post" /></p>
<img alt="Image for post" src="https://miro.medium.com/max/2732/1*O2HJGDQaLjiL4ePKNdHHWw.png" />


<p>You can view the app here: <a target="_blank" href="https://angulardevops.herokuapp.com/">https://angulardevops.herokuapp.com/</a></p>
<p><img src="https://miro.medium.com/max/60/1*P_cruJ70nMbv363xAy8cKg.png?q=20" alt="Image for post" /></p>
<img alt="Image for post" src="https://miro.medium.com/max/2732/1*P_cruJ70nMbv363xAy8cKg.png" />


<p>Finally, this tutorial is aimed to help developers and DevOps engineers to deploy an Angular app, I hope it is helpful 😍. for any feedback please contact me!</p>
<p><em>If this post was helpful click the clap button as much as possible 😃.</em></p>
<h1 id="thank-you">Thank you 😃</h1>
]]></content:encoded></item><item><title><![CDATA[Optimizing CI/CD Pipeline for Rust Projects (Gitlab & Docker)]]></title><description><![CDATA[Before we begin, I would like to thank Astrolab-Agency for the Internship opportunity and for their trust in me to make this project, I would like to thank Mr Mahdi Ben Chikh for his precious support along the intern period.

What is Rust ?
Rust is a...]]></description><link>https://blog.hatembentayeb.dev/optimizing-ci-cd-pipeline-for-rust-projects-gitlab-docker</link><guid isPermaLink="true">https://blog.hatembentayeb.dev/optimizing-ci-cd-pipeline-for-rust-projects-gitlab-docker</guid><category><![CDATA[Docker]]></category><category><![CDATA[Devops]]></category><category><![CDATA[GitLab]]></category><category><![CDATA[Rust]]></category><category><![CDATA[ci-cd]]></category><dc:creator><![CDATA[hatem ben tayeb]]></dc:creator><pubDate>Sun, 03 Jan 2021 13:53:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1610372019871/LdiCpYN4h.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>Before we begin, I would like to thank <a target="_blank" href="https://astrolab-agency.com/">Astrolab-Agency</a> for the Internship opportunity and for their trust in me to make this project, I would like to thank Mr <a target="_blank" href="https://www.linkedin.com/in/mehdi-ben-cheikh-b8b3855a/">Mahdi Ben Chikh</a> for his precious support along the intern period.</p>
</blockquote>
<h1 id="what-is-rust"><strong>What is Rust ?</strong></h1>
<p>Rust is a programming language ( general purpose) C-like, which means it is a compiled language and it comes with new strong features in managing memory and more. The cool thing! rust does not have a garbage collector and that is <em>awesome</em> 😅 .</p>
<h1 id="what-is-devops"><strong>What is DevOps ?</strong></h1>
<p>In short, DevOps is the key feature that helps the <strong>dev</strong> team and the <strong>ops</strong> team to be friends 😃 without work conflicts, It is the ART of automation. It increases the velocity of delivering better software!</p>
<h1 id="identifying-the-problem">Identifying the problem</h1>
<p>we can make a lot of things with rust like web apps, system drivers, and much more but there is one problem which is the time that rust takes to make a binary by downloading dependencies and compile them.</p>
<p>The <strong>cargo</strong> command helps us to download packages ( crates in the rust world), The <strong>Rustc</strong> is our compiler. Now we need to make a pipeline using the Gitlab CI/CD and docker to make the <strong><em>deployment faster</em></strong>.</p>
<blockquote>
<p>This is our challenge and the Goal of this article! 👊</p>
</blockquote>
<h1 id="static-linking-vs-dynamic-linking"><strong>Static linking Vs Dynamic linking</strong></h1>
<p>Rust by default uses a Dynamic linking method to build the binary, so what is <em>dynamic linking</em> ?.</p>
<p>The Dynamic linking uses shared libraries, so the lib is loaded into the memory and only the address is integrated into the binary. In this case, the <code>libc</code> is used.</p>
<p>The Static linking uses static libraries that are integrated physically into the binary, no addresses are used and the binary size will be bigger. In this case, the <code>musl libc</code> is used.</p>
<p>You want to know more ? Then check this : <a target="_blank" href="/@dkwok94/the-linking-process-exposed-static-vs-dynamic-libraries-977e92139b5f">click here</a>.</p>
<h1 id="optimizing-the-cicd-pipeline">Optimizing the CI/CD pipeline</h1>
<p>The CI/CD pipeline is a set of steps that allow us to make :</p>
<blockquote>
<p>build → test → deploy</p>
</blockquote>
<p>In this article, I will focus on the build stage because in my opinion it is a very sensitive phase and it will affect the “Time to market” approach!</p>
<p>So the first thing is to optimize the size of our docker images to make the deployment faster. Before we begin, I will use a simple rust project for the demo.</p>
<p><img src="https://miro.medium.com/max/60/1*z2-pMILSKWMFE9oGAV3mPA.png?q=20" alt="project structure" /></p>
<img alt="project structure" src="https://miro.medium.com/max/2732/1*z2-pMILSKWMFE9oGAV3mPA.png" />

<p>The project structure</p>
<p>let’s understand the project structure :</p>
<ul>
<li><strong>src</strong> : This dir contains all source code of the app (<strong>*.rs</strong> files).</li>
<li><strong>Cargo.toml</strong>: This file contains the package meta-data and the dependencies required by the app and some other features ….</li>
<li><strong>Cargo.lock</strong> : Ct contains the exact information about your dependencies.</li>
<li><strong>Rocket.toml</strong>: With this file, we specify the app status ( development, staging, or production) and the required configuration for each mode, for example, the port configuration for each environment.</li>
<li><strong>Dockerfile</strong>: This is the docker file configuration to build the image with the specific environment that is configured already in <em>Rocket.toml.</em></li>
</ul>
<p>Are you prepared 👊 😈 !!! , let’s begin the show !! 🎉 🎉 🎉</p>
<p>We will begin by building the app image locally, so let’s see how the docker file looks like :</p>
<p>This Dockerfile is split into two sections :</p>
<ul>
<li>The builder section ( a temporary container)</li>
<li>The final image (Reduced in size)</li>
</ul>
<p><strong>The builder section:</strong></p>
<p>In order to use rust, we have to get pre-configured images that contain the Rustc compiler and the Cargo tool. the image has the rust nightly build version and this is a real challenge because it’s not stable 😠.</p>
<blockquote>
<p>We will use the static linking to get a fully functional binary that doesn’t need any shared libraries from the host image !!</p>
</blockquote>
<p>let’s breakdown the code :</p>
<ul>
<li>First we import the base image.</li>
<li>We need the <em>MUSL</em> support: <code>musl-tool</code> after updating the <strong>source.list</strong> of your packages <code>apt-get update</code>, <em>MUSL</em> is an easy-to-deploy static and minimal dynamically linked programs.</li>
<li>Now we have to specify the target if you don’t know! no problem! you can use <code>x86_64-unknown-Linux-musl</code>, run with Rustup (<em>the rust toolchain installer</em>)</li>
<li>To define the project structure on the container we use <code>cargo new --bin material</code> (material is the project name), it’s much like the structure that we see earlier.</li>
<li>Making the <code>material</code> directory as a default we use the <code>WORKDIR</code> Dockerfile command.</li>
<li>The <code>Cargo.toml</code> and <code>Cargo.lock</code> are required for deps. installation</li>
<li>Setting up the <code>RUST_FLAGS</code> with <code>-Clinker=musl-GCC</code>: this flag tells cargo to use the musl GCC to compile the source code, the <code>--release</code> argument is used to prepare the code for a release ( <em>final binary optimization</em>).</li>
<li><code>--target</code> specify the target compilation 64 or 32 bit</li>
<li><code>--feature vendored</code> this command is an angle 😄 ! it helps to solve any SSL problem by finding the SSL resources automatically without specifying the SSL lib directory and the SSL include directory. It saves me a lot of time, this command is associated with some configurations in the Cargo.toml file under the <code>feature</code> section.</li>
</ul>
<blockquote>
<p>Until now we only build the dependencies in <strong><em>Cargo.toml</em></strong> and we make <strong>the clean</strong> ( removing unnecessary files)</p>
</blockquote>
<ul>
<li>After downloading and compiling required packages, it’s time to get the source code into the container and make the final build to produce the final binary ( <strong>standalone</strong>).</li>
</ul>
<p>The builder stage has complete! congrats 😙 🎉 yeah !!. Now let’s use alpine as a base image to get the binary from the build stage, but! wait for a second ! what is alpine ???</p>
<blockquote>
<p>Alpine is a Linux distribution, it’s characterized in the docker world by its size! it is a very small image (4MB) and it contains only the base commands (busybox)</p>
</blockquote>
<ul>
<li><code>--from=cargo-build ..../material</code> now we will copy the final binary to the alpine and the intermediate container (cargo-build) will be destroyed and we get as a result a very tiny image (12–20MB) ready to use 😃 😃 😃</li>
</ul>
<blockquote>
<p>You know how to build a docker image right 😲 ? okay 😃</p>
</blockquote>
<h1 id="the-cicd-pipeline">The CI/CD pipeline</h1>
<p>After testing the image locally, it seems good 😃, we resolve the docker image size, but in the CI system the velocity is very important than size !! so let’s take this challenge and reduce the compilation time of this rust project !!</p>
<p>let’s look at the <strong>.gitlab-ci.yml</strong> file ( _our CI confi_guration):</p>
<p>There is a tip in this file, I just split the docker file into two stages in this .gitlab-ci.yml :</p>
<ul>
<li>The builder stage (rustdocker/rust..)→ build dependencies and binary</li>
<li>The final stage (Alpine) → the build stage</li>
</ul>
<p>For the CI work, I prepared a ready-to-use Docker image that contains all I need to make a reliable and fast pipeline for the rust project, this image is hosted in my <strong>docker hub .</strong></p>
<blockquote>
<p>hatembt/rust-ci:latest</p>
</blockquote>
<p>This image contains the following packages installed and configured :</p>
<ul>
<li>The <code>sccache</code> command: this command caches the compiled dependencies! so by making this action to our build we can compile deps only one time !! 😅 , and we gained much more time.</li>
<li>The <code>cargo-audit</code>: it’s a helpful command lets us to scan dependencies security.</li>
</ul>
<p>Let’s breakdown the code and understand what’s going on !!</p>
<p>In the first job : <strong>_prepare_deps_for<em>cargo</em></strong> we need our base image <strong>hatembt/rust-ci .</strong></p>
<p>In this job some setting are required to make a successful build are placed in the <strong>before_script:</strong></p>
<ul>
<li>Defining the cargo home in the path variable.</li>
<li>Defining the cache directory that s generated by <code>sccache</code> (it contains the compilation cache ).</li>
<li>Adding cargo and rustup ( tey are under .cargo/bin) in the path.</li>
<li>Specifying the <code>RUSTC_WRAPPER</code> variable in order to use the <code>sccache</code> command with the rustc or MUSL in our case.</li>
</ul>
<p>Now all thing are ready! so let’s make the build in the <strong>script</strong> section, you are already now what we should do 😃 , let’s skip it 👇.</p>
<p>The <strong>cache and artifacts</strong> sections are very important ! its saves the data under :</p>
<ul>
<li>.cargo/</li>
<li>.cache/sccache</li>
<li>target/x86_64-unknown-linux-musl/release/material (this is our final binary ).</li>
</ul>
<p>To know more about caching and artifacts flow this <a target="_blank" href="https://gitlab.com/gitlab-org/gitlab-runner/issues/1232">link</a>.</p>
<p>All data that is created in the first run of the CI jobs will be now saved and uploaded to the Gitlab coordinator. On the next build (new codes are pushed), we will not start the build from scratch, we just build the new packages, the old data will be injected with <code>&lt;&lt;:*caching_rust</code> after the <strong>image</strong> keyword.</p>
<p>let’s move on to the next JOB: <strong>build_docker_image:</strong></p>
<p>I made a new Dockerfile for the docker build stage, it’s based on the alpine image and it contains only the <strong>binary</strong> from the previous stage.</p>
<p>The new Dockerfile:</p>
<p>First, we need a docker in docker image (dind) → <em>to get the docker command</em> and let’s take the steps below:</p>
<ul>
<li>login to the Gitlab registry</li>
<li>Build the image with the new Dockerfile</li>
<li>Push the image to Gitlab registry</li>
</ul>
<p>and Now the results! 😧</p>
<p><strong><em>The image size is :</em></strong></p>
<p><img src="https://miro.medium.com/max/60/1*K_OvE1WhvDFMFMETip-Lig.png?q=20" alt="Image for post" /></p>
<img alt="Image for post" src="https://miro.medium.com/max/2604/1*K_OvE1WhvDFMFMETip-Lig.png" />

<p>image size</p>
<p><strong><em>The CI Time:</em></strong></p>
<p><img src="https://miro.medium.com/max/40/1*mLua8DsSzzvL6nmW5zYLyw.png?q=20" alt="Image for post" /></p>
<img alt="Image for post" src="https://miro.medium.com/max/778/1*mLua8DsSzzvL6nmW5zYLyw.png" />

<blockquote>
<p>NB: the time is for the whole build time, the built binary and docker_build stages</p>
</blockquote>
<p>This is the power of DevOps, the art of automation with some <strong>philosophy</strong> in the configurations, and the steps to flow we can make even better than these results.</p>
<p>In business the velocity, the quality, and the necessary features (on the application) are very important to Bring the company to high levels of success → this is the successful <strong>Digital transformation.</strong></p>
<p>Finally, I hope that this Story helps you to move on to the next steps in the CI/CD systems, you can apply these ideas into any language (mostly compiled languages, but still the same steps). If you have any feedback or critiques, please feel free to share them with me. If this walkthrough helped you, please like 👏 the article and connect with me on <a target="_blank" href="https://www.linkedin.com/in/hatembentayeb/">LinkedIn</a>.</p>
<h1 id="thank-you">Thank you 😄</h1>
]]></content:encoded></item><item><title><![CDATA[Hello Docker 😃 — Part II]]></title><description><![CDATA[Hello to the second part of the “hello docker “ series, if your are new on docker please check the previous part by following this hello docker 😃 — Part I, In this lecture i will show you more advanced feature of the docker command line, also we wil...]]></description><link>https://blog.hatembentayeb.dev/hello-docker-part-ii</link><guid isPermaLink="true">https://blog.hatembentayeb.dev/hello-docker-part-ii</guid><category><![CDATA[Docker]]></category><category><![CDATA[Devops]]></category><category><![CDATA[containers]]></category><category><![CDATA[Flask Framework]]></category><category><![CDATA[Linux]]></category><dc:creator><![CDATA[hatem ben tayeb]]></dc:creator><pubDate>Sun, 03 Jan 2021 13:51:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1610152128599/6xkL5rIiO.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>Hello to the second part of the “<em>hello docker “</em> series, if your are new on docker please check the previous part by following this <a target="_blank" href="https://hatembentayeb.hashnode.dev/hello-docker-part-i"><em>hello docker 😃 — Part I</em></a><em>,</em> In this lecture i will show you more advanced feature of the docker command line, also we will create a basic dockerized project for a simple python app with flask and we will push to <a target="_blank" href="https://hub.docker.com/">dockerhub</a>.</p>
</blockquote>
<h1 id="common-docker-commands">Common Docker commands</h1>
<p>In this section I am going to show you the most used docker commands so let’s begin :</p>
<ul>
<li>To get info about your docker environment use : <code>docker info</code> </li>
<li>To remove a container use : <code>docker rm &lt;name | ID&gt;</code></li>
<li>To remove an entire image use : <code>docker rmi &lt;name | ID&gt;</code></li>
<li>To remove a container after run use : <code>docker run --rm &lt;name|ID&gt;</code> </li>
<li>To view current running containers use : <code>docker ps</code></li>
<li>To view all running and exited containers use: <code>docker ps -a</code></li>
<li>To view all and only the IDs of containers use : <code>docker ps -a -q</code> </li>
<li>To get all images use : <code>docker images</code> </li>
<li>To remove all images use : <code>docker rm $(docker ps -a -q)</code></li>
<li>To view the <em></em> images use : <code>docker images -f"dangling=true"</code></li>
<li>To remove them use : <code>docker rmi $(docker images -f"dangling=true" -q)</code> </li>
<li>To run the container in background use : <code>docker run -d ... &lt;name|ID&gt;</code></li>
</ul>
<p>That’s enough for now 😆, no lets build a simple python project with flask</p>
<h1 id="the-flask-app">The Flask app</h1>
<p>Flask is a simple and powerful Framework for python like apache or tomcat... So let's begin :</p>
<p>The <code>app.py</code> file</p>
<pre><code class="lang-python">
<span class="hljs-keyword">from</span> flask <span class="hljs-keyword">import</span> Flask
app = Flask(__name__)

<span class="hljs-meta">@app.route("/")</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">hello</span>():</span>
        <span class="hljs-keyword">return</span> <span class="hljs-string">"Hello My Name is Hatem"</span>

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    app.run(host=<span class="hljs-string">"0.0.0.0"</span>, port=<span class="hljs-number">8080</span>)
</code></pre>
<p>Make sure to install <code>flask</code> with <code>pip install flask</code> and run the script with <code>python app.py</code> and open your browser on <code>localhost:8080/</code> .</p>
<p>Let’s generate the <code>requirements.txt</code> , we will not use <code>pip freeze</code> here (we are not in a virtual environment) but we will use <code>pipreqs</code> and make sure to install it with <code>pip install pipreqs</code> .</p>
<p>use <code>pipreqs &lt;path to the python project&gt;</code> to generate it.</p>
<p>Now let’s write the Dockerfile :</p>
<p>Dockerfile</p>
<pre><code class="lang-yaml"><span class="hljs-string">FROM</span> <span class="hljs-string">python:latest</span>
<span class="hljs-string">WORKDIR</span> <span class="hljs-string">/app</span>
<span class="hljs-string">COPY</span> <span class="hljs-string">requirements.txt</span> <span class="hljs-string">.</span>
<span class="hljs-string">RUN</span> <span class="hljs-string">pip</span> <span class="hljs-string">install</span> <span class="hljs-string">-r</span> <span class="hljs-string">requirements.txt</span>
<span class="hljs-string">COPY</span> <span class="hljs-string">app.py</span> <span class="hljs-string">.</span>
<span class="hljs-string">CMD</span> <span class="hljs-string">python</span> <span class="hljs-string">app.py</span>
</code></pre>
<ul>
<li><code>FROM python:latest</code> : using python as a base image.</li>
<li><code>WORKDIR /app</code> : using <em>/app</em> as a default directory.</li>
<li><code>COPY requirements.txt .</code> compy this file to <em>/app</em> <code>RUN pip install -r req..txt</code> : install the required dependencies</li>
<li><code>COPY app.py .</code> : copy the app script to <em>/app</em> </li>
<li><code>CMD python app.py</code> : The container entry point when we run it</li>
<li>Run the build process with <code>docker build -t hello_flask .</code></li>
</ul>
<p>Run the container <code>docker rn --rm -p 8080: 8080 hello_flask</code> , the <code>-p 8080:8080</code> : <em>will map the port</em> <em>8080 on your host to the port 8080 in your container → this is the port mapping in docker, you can change the host port to any port you want and must be &gt; 1024 ( ports &lt; 1024 are reserved by the system).</em></p>
<h1 id="environment-variables-in-docker">Environment variables in Docker</h1>
<p>An <a target="_blank" href="https://en.wikipedia.org/wiki/Environment_variable"><em>environment variable</em></a> is a variable whose value is set outside the program, typically through the functionality built into the operating system or microservice. An environment variable is made up of a name/value pair, and any number may be created and available for reference at a point in time.
→ <a target="_blank" href="/chingu/an-introduction-to-environment-variables-and-how-to-use-them-f602f66d15fa">source</a> for more information.</p>
<p>let’s use them in our example :</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> os 
<span class="hljs-meta">@app.route("/")</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">hello</span>():</span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">"Hello My Name is {}"</span>.format(os.environ[<span class="hljs-string">'NAME'</span>])
</code></pre>
<p>the <code>os.environ['NAME']</code> will fetch the <code>NAME</code> variable and get its value and return it in the browser.
make sure to build the container again and run it like this :
<code>docker run --rm -p 8080:8080 -e "NAME=steve" hello_flask</code></p>
<h1 id="volumes-with-docker">Volumes with Docker</h1>
<p>In order to be able to save (persist) data and also to share data between containers, <strong>Docker</strong> came up with the concept of <strong>volumes</strong>. Quite simply, <strong>volumes</strong> are directories (or files) that are outside of the default Union File System and exist as normal directories and files on the host filesystem.</p>
<p>→ <a target="_blank" href="https://blog.container-solutions.com/understanding-volumes-docker">source</a></p>
<p>let’s make some change to our script :</p>
<pre><code class="lang-python"><span class="hljs-meta">@app.route("/name_from_file")</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">name_from_file</span>():</span>
    <span class="hljs-keyword">with</span> open(<span class="hljs-string">"files/name.txt"</span>,<span class="hljs-string">"r"</span>) <span class="hljs-keyword">as</span> file :
        name= file.readline()
    <span class="hljs-keyword">return</span> <span class="hljs-string">"Hello My Name is {}"</span>.format(name)
</code></pre>
<p>Make sure to add a directory named <em>“files”</em> and add a file named <em>“name.txt”</em> that contains your name or whatever you want.</p>
<p>Now make the build again ! and run it with this command :</p>
<pre><code class="lang-bash">docker run --rm -v <span class="hljs-variable">${PWD}</span>/files:/app/files -e <span class="hljs-string">"NAME=steve"</span> -p 8080:8080 hello_flask
</code></pre>
<p>Now open your browser and type <code>localhost:8080/name_from_file</code> , you should see the name that you already add it in the <em>name.txt</em> file. In this case, you can change the content of the file in real-time and reload the page, you should see the changes 😆.</p>
<h1 id="saving-and-publishing-images">Saving and publishing images</h1>
<p>After finalizing your work there are two things you should do :</p>
<ul>
<li>Saving images to tarballs or compressed archives</li>
<li>Publishing images to registries to be used in public or private…</li>
</ul>
<p>Let's begin by saving our example image to a tarball by running this command <code>docker save --output hello_flask.tar hello_flask</code> , now, check your current directory and type in the terminal <code>ls -sh hello_flask.tar</code> to get the archive size. if you want to reduce the archive size use the <code>gzip</code> command which is compression tools. Execute this command :</p>
<pre><code class="lang-bash">docker save hello_flask | gzip &gt; hello_flask.tar.gz
</code></pre>
<p>and check the size again 😃. you can now upload them to your cloud storage or wherever you want. Now if you want to load the tar file to the docker engine simply run :</p>
<pre><code class="lang-bash">docker image load -i hello_flask.tar
</code></pre>
<p>Docker hub is our target now, to publish images in it you have to make an account first. the image name must be with this syntax <code>account_name/image_name:image_tag</code> so let's rename our image by running this command :</p>
<pre><code class="lang-bash">docker tag hello_flask hatembt/hello_flask:latest
</code></pre>
<p>Now you have to log in to your account and push the image to the public :</p>
<pre><code class="lang-bash">docker login 
docker push hatembt/hello_flask:latest
</code></pre>
<p>To pull the image just run <code>docker pull hatembt/hello_flask:latest</code> , no need for credentials here 😃 .</p>
<p><strong>Finally</strong>, I hope that this tutorial is helpful to everyone who wants to know docker. In Part III, we will go through a complex project ( Front-end + Back-end + Database) and we will use docker-compose as an orchestrator. If you have any feedback please share it with me <a target="_blank" href="https://www.linkedin.com/in/hatembentayeb/">Hatem Ben Tayeb</a> 😆.</p>
<h1 id="thank-you">Thank you 😃</h1>
]]></content:encoded></item><item><title><![CDATA[Hello Docker 😄 —Part I]]></title><description><![CDATA[In this story i will share my docker experience, so, be ready ! to know the most used container technologies in the hole world ! . I will help you to get started in docker also we will deep dive into cool features … , and by the end of this story you...]]></description><link>https://blog.hatembentayeb.dev/hello-docker-part-i</link><guid isPermaLink="true">https://blog.hatembentayeb.dev/hello-docker-part-i</guid><category><![CDATA[Docker]]></category><category><![CDATA[containers]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Linux]]></category><dc:creator><![CDATA[hatem ben tayeb]]></dc:creator><pubDate>Sun, 03 Jan 2021 13:49:45 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1610328369906/Pe0tBgjOW.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>In this story i will share my docker experience, so, be ready ! to know the most used container technologies in the hole world ! . I will help you to get started in docker also we will deep dive into cool features … , and by the end of this story you will be able to make your own projects using docker and docker-compose 😅.
The second part is ready : <a target="_blank" href="https://hatembentayeb.hashnode.dev/hello-docker-part-ii">hello docker part II</a></p>
</blockquote>
<h1 id="what-is-docker"><strong>What is Docker ?</strong></h1>
<p>In a simple way … Docker is a way to bundle an app and it’s requirements into a single Image that can be runnable in any environment like <em>Windows</em>, <em>Linux</em>, <em>Mac</em> <em>os</em> and of course the cloud.</p>
<p>A docker Image is a micro os like <em>ubuntu</em>, <em>debian</em> and <em>archlinux</em> …, But what is a micro os ? simply it’s a very tiny os that contains only a file system and some basic command and of course a package manager like <em>apt</em> and <em>pacman</em> …</p>
<h1 id="what-i-need-to-get-started">What i need to get started ?</h1>
<p>I am not going through the installation process or explaining the functional part of docker … i will go through examples from real life and some cool tips and tricks .</p>
<ul>
<li>Docker installation : <a target="_blank" href="https://docs.docker.com/install/linux/docker-ce/ubuntu/">Clik here</a></li>
<li>Docker vs virtual machine : <a target="_blank" href="https://www.backblaze.com/blog/vm-vs-containers/">Click here</a></li>
</ul>
<blockquote>
<p>You must READ them 😠</p>
</blockquote>
<p>Check your docker status by running this command :</p>
<blockquote>
<p>$ sudo systemctl status docker</p>
</blockquote>
<p>If docker is not running make sure to run this command</p>
<blockquote>
<p>$ sudo systemctl start docker</p>
</blockquote>
<h1 id="running-your-first-container">Running your first container</h1>
<p>After installing docker, now you are able to follow this article, so … let’s run our first container by running this command and i will explain every step 😃</p>
<blockquote>
<p>$ docker run hello-world</p>
</blockquote>
<p>This is the output :</p>
<p><img src="https://miro.medium.com/max/60/1*nTWq4BuE0WjV2XKDpyFO2w.png?q=20" alt="Image for post" /></p>
<img alt="Image for post" src="https://miro.medium.com/max/2732/1*nTWq4BuE0WjV2XKDpyFO2w.png" />

<p>By this command we tell the docker engine to run an image called “hello-world”, which is an image that contains a simple script with the output that begins with “ Hello from docker …” so … what happens here ?</p>
<p>The docker engine searches for this image in the local registry but he tells us that he didn’t find it so it goes to a global registry which is a huge and public place for free images (created by other peoples ) called <a target="_blank" href="https://hub.docker.com/"><em>docker hub</em></a><em>.</em></p>
<p>He pull or download the image then he run it, and finally we get the message or the execution result of the script inside the image. To download only the image we use <em>’’docker pull hello-world’’.</em></p>
<p>Let’s try another one , in this case i have an ARCHLINUX based os and i want to run an alpine system with docker and make some work on it so let’s go 😄</p>
<blockquote>
<p>docker pull alpine:latest</p>
<p>docker run -it alpine sh</p>
</blockquote>
<p>Output :</p>
<p><img src="https://miro.medium.com/freeze/max/60/1*l0scPgwy2PoRydrFtNgVmQ.gif?q=20" alt="Image for post" /></p>
<img alt="Image for post" src="https://miro.medium.com/max/2724/1*l0scPgwy2PoRydrFtNgVmQ.gif" />

<p>We have downloaded the alpine image and we run it interactively by adding “ -it” and “sh” at the end :</p>
<ul>
<li><strong>-i</strong> : for the interactive mode with <code>-t</code> option ( we can give input and type some commands to interact with the alpine system)</li>
<li><strong>-t</strong> : Allocate a pseudo-TTY (PTY) to emulate a hardware terminal we used often with ssh and telnet protocols .</li>
<li><strong>sh</strong> : is the default shell ( like bash, csh and zsh …)</li>
</ul>
<p>The other commands ( ls , cat …) are a basic commands that are founded on any other Linux systems.</p>
<h1 id="commit-a-docker-container">Commit a docker container</h1>
<p>What is the difference between a docker image and a container ? Simply the docker image is the basic resource and it can be shared by multiple containers but how ? when we make a <em>“docker run”</em> , the docker engine make a snapshot or a copy of the base image in the memory and we use it like we did with alpine ,after some work on the snapshot or the container we usually stop and exit the container, and every container is identified with a unique hash (ID),</p>
<p>To add the changes that we make on the snapshot to the base image we use the “<em>commit</em>” command to persist our changes so let’s try out :</p>
<p>Watch this demo :</p>
<p><img src="https://miro.medium.com/freeze/max/60/1*tqZ0yh-yzAjZpj2uQAxaWQ.gif?q=20" alt="Image for post" /></p>
<img alt="Image for post" src="https://miro.medium.com/max/2724/1*tqZ0yh-yzAjZpj2uQAxaWQ.gif" />

<p>docker commit</p>
<p>We have created a new image named “_alpine<em>modified”</em> that contains a <em>“hello.sh”</em> fileunder the “<em>/home”</em> folder <em>.</em> You surely notice that the original modification in the alpine image is gone now.</p>
<h1 id="create-your-own-image">Create your own image</h1>
<p>Another way to save changes or create some stuff in containers is the <em>“Dockerfile” ,</em> which is a a set of instructions that define the final state of your image .</p>
<p>This file uses YAML syntax which is easy to understand and so clean 😄, so let’s go writing our first <em>Dockerfile</em> :</p>
<p>dockerfile</p>
<p>In this file we have two keywords :</p>
<ul>
<li><strong>FROM :</strong> this is the base image that we use .</li>
<li><strong>CMD :</strong> the Command keyword , this command will run the <em>“echo hello”</em> when we use the <em>“docker run  “.</em></li>
</ul>
<p>let’s build the image :</p>
<blockquote>
<p>$ docker build -t hello .</p>
</blockquote>
<ul>
<li><strong>build</strong> : will make the build process by executing every instruction (step)</li>
<li><strong>-t</strong> : giving a name to our image</li>
<li><strong>.</strong> : our build context which we have the “<em>Dockerfile”,</em> or you can specify the hole path.</li>
</ul>
<blockquote>
<p><strong>note</strong>: don’t forget the dot “.” please !!</p>
</blockquote>
<p>Watch the demo :</p>
<p><img src="https://miro.medium.com/freeze/max/60/1*f4VZ2Mno9P0CfzsZUqdGqQ.gif?q=20" alt="Image for post" /></p>
<img alt="Image for post" src="https://miro.medium.com/max/2724/1*f4VZ2Mno9P0CfzsZUqdGqQ.gif" />

<p>hello_alpine</p>
<p>To get all images try to run : <code>docker images</code> , if you want to get more information about your image run this command : <code>docker inspect hello</code> and try to figure out what happens here , i will cover this topic in the next stories … .</p>
<p>Finally, this tutorial is just an introduction to docker for beginners, in the next parts i will talk about more advanced features in docker. If you have feedback feel free to share them with me <a target="_blank" href="https://www.linkedin.com/in/hatembentayeb/">Hatem Ben Tayeb</a>.</p>
<p>If this post was helpful click the clap button as much as possible 😃.
The second part is ready : <a target="_blank" href="/@hatemtayeb2/hello-docker-part-ii-998472c6e296">Learn Docker From Scratch</a></p>
<h1 id="thank-you">Thank you 😃</h1>
]]></content:encoded></item><item><title><![CDATA[Automate your Documentation with Gitlab and Mkdocs]]></title><description><![CDATA[Producing documentation may be painful and need a lot of time to write and operate. In this story, I will share with you, my way of generating docs using the DevOps approach. To make life easier, we will explore the art of automation 😃.
Let’s go fol...]]></description><link>https://blog.hatembentayeb.dev/automate-your-documentation-with-gitlab-and-mkdocs</link><guid isPermaLink="true">https://blog.hatembentayeb.dev/automate-your-documentation-with-gitlab-and-mkdocs</guid><category><![CDATA[documentation]]></category><category><![CDATA[Docker]]></category><category><![CDATA[GitLab]]></category><category><![CDATA[Devops]]></category><category><![CDATA[ci-cd]]></category><dc:creator><![CDATA[hatem ben tayeb]]></dc:creator><pubDate>Sun, 03 Jan 2021 13:41:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1610328603211/QX6wbewo2.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>Producing documentation may be painful and need a lot of time to write and operate. In this story, I will share with you, my way of generating docs using the DevOps approach. To make life easier, we will explore the art of automation 😃.</p>
<p>Let’s go folks 😙</p>
</blockquote>
<h1 id="create-a-gitlab-repo">Create a Gitlab repo</h1>
<p>This is straightforward, follow these steps:</p>
<ul>
<li>Log in to your GitLab</li>
<li>Click new project</li>
<li>Give it a name: <code>auto_docs</code></li>
<li>Initialize it with a <code>README.md</code> file</li>
<li>Make it public or private</li>
<li>Hit create</li>
</ul>
<p>Now clone the project by copying the URL and run this command :</p>
<pre><code>$ git clone [<span class="hljs-string">https://gitlab.com/auto_docs.git</span>](<span class="hljs-link">https://gitlab.com/auto_docs.git</span>)<span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span></span>
</code></pre><h1 id="setting-up-the-environment">Setting up the environment</h1>
<p>I’m using a Linux environment but it is possible to reproduce the same steps on a Windows machine.</p>
<p>In order to follow me, you need a set of tools that must be available on your machine … make sure to have <code>python3</code> installed, I have python 3.8 (latest).</p>
<h2 id="creating-a-virtual-environment">Creating a virtual environment</h2>
<p>The easiest way to set up a virtual environment is to install <code>virtualenv</code> python package by executing <code>pip install virtualenv</code> .</p>
<p>Navigate to your local GitLab repository and create a new virtual environment.</p>
<pre><code>$ cd auto_docs/
$ virtualenv autodocs
$ source autodocs/bin/acivate&lt;<span class="hljs-regexp">/span&gt;</span>
</code></pre><h2 id="installing-mkdocs-material">Installing Mkdocs Material</h2>
<p>Make sure that the virtual environment is active.</p>
<p>Install the mkdocs material with this command: <code>pip install mkdocs-material.</code>
This package needs some dependencies to work .. install them by using a requirement.txt file, copy-paste the dependencies list to filename <code>requirements.txt</code></p>
<pre><code><span class="hljs-attribute">Babel</span>==<span class="hljs-number">2</span>.<span class="hljs-number">8</span>.<span class="hljs-number">0</span>
<span class="hljs-attribute">click</span>==<span class="hljs-number">7</span>.<span class="hljs-number">1</span>.<span class="hljs-number">1</span>
<span class="hljs-attribute">future</span>==<span class="hljs-number">0</span>.<span class="hljs-number">18</span>.<span class="hljs-number">2</span>
<span class="hljs-attribute">gitdb</span>==<span class="hljs-number">4</span>.<span class="hljs-number">0</span>.<span class="hljs-number">4</span>
<span class="hljs-attribute">GitPython</span>==<span class="hljs-number">3</span>.<span class="hljs-number">1</span>.<span class="hljs-number">1</span>
<span class="hljs-attribute">htmlmin</span>==<span class="hljs-number">0</span>.<span class="hljs-number">1</span>.<span class="hljs-number">12</span>
<span class="hljs-attribute">Jinja2</span>==<span class="hljs-number">2</span>.<span class="hljs-number">11</span>.<span class="hljs-number">2</span>
<span class="hljs-attribute">joblib</span>==<span class="hljs-number">0</span>.<span class="hljs-number">14</span>.<span class="hljs-number">1</span>
<span class="hljs-attribute">jsmin</span>==<span class="hljs-number">2</span>.<span class="hljs-number">2</span>.<span class="hljs-number">2</span>
<span class="hljs-attribute">livereload</span>==<span class="hljs-number">2</span>.<span class="hljs-number">6</span>.<span class="hljs-number">1</span>
<span class="hljs-attribute">lunr</span>==<span class="hljs-number">0</span>.<span class="hljs-number">5</span>.<span class="hljs-number">6</span>
<span class="hljs-attribute">Markdown</span>==<span class="hljs-number">3</span>.<span class="hljs-number">2</span>.<span class="hljs-number">1</span>
<span class="hljs-attribute">MarkupSafe</span>==<span class="hljs-number">1</span>.<span class="hljs-number">1</span>.<span class="hljs-number">1</span>
<span class="hljs-attribute">mkdocs</span>==<span class="hljs-number">1</span>.<span class="hljs-number">1</span>
<span class="hljs-attribute">mkdocs</span>-awesome-pages-plugin==<span class="hljs-number">2</span>.<span class="hljs-number">2</span>.<span class="hljs-number">1</span>
<span class="hljs-attribute">mkdocs</span>-git-revision-date-localized-plugin==<span class="hljs-number">0</span>.<span class="hljs-number">5</span>.<span class="hljs-number">0</span>
<span class="hljs-attribute">mkdocs</span>-material==<span class="hljs-number">5</span>.<span class="hljs-number">1</span>.<span class="hljs-number">1</span>
<span class="hljs-attribute">mkdocs</span>-material-extensions==<span class="hljs-number">1</span>.<span class="hljs-number">0</span>b<span class="hljs-number">1</span>
<span class="hljs-attribute">mkdocs</span>-minify-plugin==<span class="hljs-number">0</span>.<span class="hljs-number">3</span>.<span class="hljs-number">0</span>
<span class="hljs-attribute">nltk</span>==<span class="hljs-number">3</span>.<span class="hljs-number">5</span>
<span class="hljs-attribute">Pygments</span>==<span class="hljs-number">2</span>.<span class="hljs-number">6</span>.<span class="hljs-number">1</span>
<span class="hljs-attribute">pymdown</span>-extensions==<span class="hljs-number">7</span>.<span class="hljs-number">0</span>
<span class="hljs-attribute">pytz</span>==<span class="hljs-number">2019</span>.<span class="hljs-number">3</span>
<span class="hljs-attribute">PyYAML</span>==<span class="hljs-number">5</span>.<span class="hljs-number">3</span>.<span class="hljs-number">1</span>
<span class="hljs-attribute">regex</span>==<span class="hljs-number">2020</span>.<span class="hljs-number">4</span>.<span class="hljs-number">4</span>
<span class="hljs-attribute">six</span>==<span class="hljs-number">1</span>.<span class="hljs-number">14</span>.<span class="hljs-number">0</span>
<span class="hljs-attribute">smmap</span>==<span class="hljs-number">3</span>.<span class="hljs-number">0</span>.<span class="hljs-number">2</span>
<span class="hljs-attribute">tornado</span>==<span class="hljs-number">6</span>.<span class="hljs-number">0</span>.<span class="hljs-number">4</span>
<span class="hljs-attribute">tqdm</span>==<span class="hljs-number">4</span>.<span class="hljs-number">45</span>.<span class="hljs-number">0</span>&lt;/span&gt;
</code></pre><p>Install them all with one command: <code>pip install -r requirements.txt</code>
Now it’s time to create a new mkdocs project 😅.</p>
<p>Run this command : <code>mkdocs new .</code> and verify that you have this structure :</p>
<pre><code>|<span class="hljs-comment">--auto_docs</span>
    |<span class="hljs-comment">--- docs</span>
    |<span class="hljs-comment">--- mkdocs.yml&lt;/span&gt;</span>
</code></pre><ul>
<li>The <strong>docs</strong> folder contains the structure of your documentation, it contains subfolders and markdown files.</li>
<li>The <strong>mkdocs.yml</strong> file defines the configuration of the generated site.</li>
</ul>
<p>Let's test the installation by running this command: <code>mkdocs serve</code>. The site will be accessible on <a target="_blank" href="http://locahost:8000">http://locahost:8000</a> and you should see the initial look of the docs.</p>
<h1 id="setting-up-the-cicd">Setting up the CI/CD</h1>
<p>let’s enable le CI/CD to automate the build and the deployment of the docs. Notice that GitLab offers a feature called <code>GitLab pages</code> that can serve for free a static resource (HTML, js, CSS). The repo path is converted to an URL to your docs.</p>
<h2 id="create-the-cicd-file">Create the CI/CD file</h2>
<p>Gitlab uses a YAML file — it holds the pipeline configuration.</p>
<p>The CI file content:</p>
<pre><code><span class="hljs-attr">stages :</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">build&lt;/span&gt;&lt;span</span> <span class="hljs-string">id="3031"</span> <span class="hljs-string">class="fu</span> <span class="hljs-string">lb</span> <span class="hljs-string">ji</span> <span class="hljs-string">ex</span> <span class="hljs-string">kt</span> <span class="hljs-string">b</span> <span class="hljs-string">lc</span> <span class="hljs-string">mb</span> <span class="hljs-string">mc</span> <span class="hljs-string">md</span> <span class="hljs-string">me</span> <span class="hljs-string">mf</span> <span class="hljs-string">le</span> <span class="hljs-string">s</span> <span class="hljs-string">lf"&gt;pages:</span>
  <span class="hljs-attr">stage:</span> <span class="hljs-string">build</span>
  <span class="hljs-attr">image:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">squidfunk/mkdocs-material</span>
  <span class="hljs-attr">entrypoint:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">""</span>
  <span class="hljs-attr">script:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">mkdocs</span> <span class="hljs-string">build</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">mv</span> <span class="hljs-string">site</span> <span class="hljs-string">public</span>
  <span class="hljs-attr">artifacts:</span>
    <span class="hljs-attr">paths:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">public</span>
  <span class="hljs-attr">only:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">master</span>
  <span class="hljs-attr">tags:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">gitlab-org-docker&lt;/span&gt;</span>
</code></pre><p>This pipeline uses a docker executor with an image that contains <code>mkdocs</code> already installed… mkdocs build the project and put the build assets on a folder called <code>site</code> … to be able to use GitLab pages you have to name your job <code>pages</code> and put the site assets into a new folder called <code>public.</code></p>
<p>For tags: check the runner's section under <strong>settings → CI/CD →Runners</strong> and pick one of the shared runners that have a tag <em>GitLab-org-docker.</em></p>
<p>All things were done 🎉 🎉 😸 !</p>
<p>Oh! just one thing … we forgot the virtual environment files .. they are big and not needed on the pipeline … they are for the local development only. The mkdocs image on the pipeline is already shipped with the necessary packages.</p>
<p>So … create a new file called <code>.gitignore</code> and add these lines:</p>
<pre><code>auto_docs/ 
requirements.txt<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
</code></pre><p>The <strong>auto_docs</strong> folder has the same name as the virtual environment .. don't forget 😠! you will be punished by pushing +100Mi 😝 and you will wait your whole life to complete the process haha 😢.</p>
<p>Now run <code>git add. &amp;&amp; git commit -m "initial commit" a &amp;&amp; git push</code> … go to your GitLab repo and click <strong>CI/CD → pipelines,</strong> click on the blue icon and visualize the logs .. once the job succeeded, navigate to <strong>settings -&gt; pages</strong> and click the link of your new documentation site (you have to wait for 10m~ to be accessible)</p>
<blockquote>
<p>Finally, I hope this was helpful! thanks for reading 😺 😍!</p>
</blockquote>
<p><img src="https://miro.medium.com/max/60/0*Piks8Tu6xUYpF4DU?q=20" alt="Image for post" /></p>
]]></content:encoded></item><item><title><![CDATA[Delivering React .. The hard way !]]></title><description><![CDATA[In  this Post  we will setup a React pipeline using Gitlab,Ansible and docker. we will go throught the whole process from nothing to a fast, reliable and hightly customizable pipeline with multi-environment deployment. 

 Daah ! let's start i can't w...]]></description><link>https://blog.hatembentayeb.dev/delivering-react-the-hard-way</link><guid isPermaLink="true">https://blog.hatembentayeb.dev/delivering-react-the-hard-way</guid><category><![CDATA[Devops]]></category><category><![CDATA[React]]></category><category><![CDATA[GitLab]]></category><category><![CDATA[Docker]]></category><category><![CDATA[ansible]]></category><dc:creator><![CDATA[hatem ben tayeb]]></dc:creator><pubDate>Sun, 03 Jan 2021 12:22:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1609674874960/o2gAXPI4N.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p>In  this Post  we will setup a React pipeline using Gitlab,Ansible and docker. we will go throught the whole process from nothing to a fast, reliable and hightly customizable pipeline with multi-environment deployment. </p>
</blockquote>
 <h2><strong>Daah ! let's start i can't wait     !</strong></h2>

<h2 id="tools">Tools</h2>
<p>Before we start we need to define the damn   tech stack :</p>
<ol>
<li><p><strong>Gitlab</strong> : GitLab is a web-based DevOps lifecycle tool that provides a Git-repository manager providing wiki, issue-tracking and continuous integration and deployment pipeline features.</p>
</li>
<li><p><strong>Ansible</strong> : Ansible is the simplest way to automate apps and IT infrastructure. Application Deployment + Configuration Management + Continuous Delivery.</p>
</li>
<li><p><strong>Docker</strong>: Docker is a tool designed to make it easier to create, deploy, and run applications by using containers.</p>
</li>
</ol>
<blockquote>
<p><em>Note: if you dont know nothing about those tools ... No problem .... aaah actually it's a problem  ... we are diving into advanced topic here  ...</em></p>
</blockquote>
<h2>Come on ! i was kidding  </h2>
<h6> Actually no ... <br />contact me if you need some support</h6>

<h2 id="architecture"><strong>Architecture</strong></h2>
<p>yoo .. we have to draw the global environment architecture  to get the whole picture about what we will do here  ... dont start coding directly. Daaah  ... you have to think by compiling the whole process in mind </p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/vhwnfek050khjp99gcpy.png" alt="Alt Text" /></p>
<p>Of course we will create a repository ( i will not explain that ) on gitlab with a hello world react app  ( i will not explain that ) and push it there. </p>
<p>Let's break down the architecture now : </p>
<ul>
<li><p><strong>Block 1</strong> : this where our code application resides and the whole gitlab eco-system also, all configuration to start a pipeline must be there, actually you can install gitlab on your own servers .. but it's not the aim of this post.</p>
</li>
<li><p><strong>Block 2</strong>: this is the important block for now (The CI environment)  .. actually it is the server when all the dirty  work resides like buiding docker containers .. saving cache ... testing code and so on ... we must configure this environment with love  haha yeah with love ... it's is the base of the pipeline speed and low level configurations.</p>
</li>
<li><p><strong>Block 3</strong> : the target environments where we will deploy our application using ansible playbooks via a secure tunnel .. <strong>SSH</strong> ... BTW i love you SSH  because we will not install any runners on those targets servers we will interact with them only with ansible to ensure a clean deployment.</p>
</li>
</ul>
<h2 id="ci-environment"><strong>CI environment</strong></h2>
<p>In this section we will connect our gitlab repo to the <strong>CI environment machine</strong> and install the gitlab runner on it of course. </p>
<ol>
<li><p>Go to your repo ... under <code>settings --&gt;  CI/CD --&gt; runners</code> and get the gitlab url and the token associeted to ... dont loose it :expressionless:</p>
</li>
<li><p>You should have a VPS or a virtual machine on the cloud ... i will work on an azure virtual machine with ubuntu 18.04 installed </p>
</li>
<li>Install docker of course ... it's simple <a target="_blank" href="https://docs.docker.com/engine/install/ubuntu/">come here</a></li>
<li>Installing the gitlab runner :</li>
</ol>
<pre><code class="lang-bash">curl -LJO <span class="hljs-string">"https://gitlab-runner-downloads.s3.amazonaws.com/latest/deb/gitlab-runner_&lt;arch&gt;.deb"</span>

dpkg -i gitlab-runner_&lt;arch&gt;.deb
</code></pre>
<p>Gitlab will be installed as service on your machine but i don't you can encounter a problem when starting it ... (don't ask me i don't know  ) so you can start it as follow : </p>
<pre><code class="lang-bash">gitlab runner run &amp; <span class="hljs-comment"># it will work on background</span>
</code></pre>
<p>You can now register the runner with <code>gitlab-runner register</code> and follow the instructions ... dont loose the token or reset it ... if you reset the token you have to re-register the runner again. i will make things easier ...  here is my <code>config.toml</code> under <code>/etc/gitlab-runner/config.toml</code></p>
<pre><code class="lang-toml"><span class="hljs-attr">concurrent</span> = <span class="hljs-number">9</span> 
<span class="hljs-attr">check_interval</span> = <span class="hljs-number">0</span>

<span class="hljs-section">[session_server]</span>
  <span class="hljs-attr">session_timeout</span> = <span class="hljs-number">1800</span>

<span class="hljs-section">[[runners]]</span>
  <span class="hljs-attr">name</span> = <span class="hljs-string">"runner-name"</span>
  <span class="hljs-attr">url</span> = <span class="hljs-string">"https://gitlab.com/"</span>
  <span class="hljs-attr">token</span> = <span class="hljs-string">"runner-token"</span>
  <span class="hljs-attr">executor</span> = <span class="hljs-string">"docker"</span>
  <span class="hljs-attr">limit</span> = <span class="hljs-number">0</span>
  <span class="hljs-section">[runners.custom_build_dir]</span>
  <span class="hljs-section">[runners.cache]</span>
    <span class="hljs-section">[runners.cache.s3]</span>
    <span class="hljs-section">[runners.cache.gcs]</span>
    <span class="hljs-section">[runners.cache.azure]</span>
  <span class="hljs-section">[runners.docker]</span>
    <span class="hljs-attr">pull_policy</span> = <span class="hljs-string">"if-not-present"</span>
    <span class="hljs-attr">tls_verify</span> = <span class="hljs-literal">false</span>
    <span class="hljs-attr">image</span> = <span class="hljs-string">"alpine"</span>
    <span class="hljs-attr">privileged</span> = <span class="hljs-literal">true</span>
    <span class="hljs-attr">disable_entrypoint_overwrite</span> = <span class="hljs-literal">false</span>
    <span class="hljs-attr">oom_kill_disable</span> = <span class="hljs-literal">false</span>
    <span class="hljs-attr">disable_cache</span> = <span class="hljs-literal">false</span>
    <span class="hljs-attr">volumes</span> = [<span class="hljs-string">"/cache:/cache"</span>]
    <span class="hljs-attr">shm_size</span> = <span class="hljs-number">0</span>
</code></pre>
<p>let's make a breakdown here ... </p>
<p>This runner will run 9 concurent jobs on a docker containers (docker in docker)  based on the alpine container (<em>to make a clean build</em>) ... The runner will pull new versions of images if they are not present ... This is optional you can turn it to <strong>always</strong> but we need to speed up the build ... No need to pull the same image again and again if there is no updates ... The runner will save the cache  on the current machine under <code>/cache</code> on the host and pass it in use as a docker volume  to save some minutes when gitlab by default upload the zipped cache to it's own storage and download it again ... It's painfull when the cache is becoming huge. At some point on time the cache will be so big .. So you can make your hand dirty and delete the shit </p>
<h2><strong>We are almost done !</strong><h2></h2></h2> 

<p>Now you can go the repository under <code>settings --&gt;  CI/CD --&gt; runners</code>  and verify that the runner was registred successfully ( <em>the green icon</em> ) </p>
<h2><strong>. . .</strong><h2></h2></h2> 


<h2 id="the-react-pipeline"><strong>The react pipeline</strong></h2>
<p>let's code the pipeline now  .... wait a second !!! we need the architecture as the previous section ... so here is how the pipeline will look like ... </p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/sxnd34ckr2arhymhm0xv.png" alt="Alt Text" /></p>
<p>This pipeline aims to support the folowing features : </p>
<ul>
<li>Caching node modules for faster build</li>
<li>Docker for shiping containers</li>
<li>Gitlab private registry linked to the repo </li>
<li>Ship only <code>/build</code> on the container with nginx web server</li>
<li>Tag containers with the git SHA-COMMIT</li>
<li>Deploy containers with an ansible playbook</li>
<li>SSH configuration as a gitlab secret to secure the target IP</li>
<li>Only ssh keypairs used for authentication with the target server ... no damn passwords  ... </li>
</ul>
<h2><strong>. . .</strong><h2></h2></h2> 

<h2 id="defining-secrets"><strong>Defining Secrets</strong></h2>
<p>This pipeline needs some variables to be placed in gitlab as secrets on <code>settings --&gt; CI/CD --&gt; Variables</code> : </p>
<table>
<thead>
<tr>
<td>Variable name</td><td>Role</td><td>Type</td></tr>
</thead>
<tbody>
<tr>
<td>ANSIBLE_KEY</td><td>The target server ssh private key</td><td>file</td></tr>
<tr>
<td>GITLAB_REGISTRY_PASS</td><td>Gitlab registry password (your account password )</td><td>variable</td></tr>
<tr>
<td>GITLAB_REGISTRY_USER</td><td>Gitlab registry login  (your account user )</td><td>variable</td></tr>
<tr>
<td>SSH_CFG</td><td>The regular ssh config that contains the target IP</td><td>file</td></tr>
</tbody>
</table>
<p>The <code>SSH_CFG</code> looks like this : </p>
<pre><code class="lang-ssh">Host *
   StrictHostKeyChecking no

Host dev 
    HostName &lt;IP&gt;
    IdentityFile ./keys/keyfile
    User root

Host staging 
    HostName &lt;IP&gt;
    IdentityFile ./keys/keyfile
    User root

Host prod 
    HostName &lt;IP&gt;
    IdentityFile ./keys/keyfile
    User root
</code></pre>
<p>I will not explain this  ... <a target="_blank" href="https://linuxize.com/post/using-the-ssh-config-file/">come here</a></p>
<h2><strong>. . .</strong><h2></h2></h2> 

<h2><strong><a target="_blank" href="https://bongo.cat/">KNOCK KNOCK</a> ... are you still here </strong><h2></h2></h2> 

Thank god  ! his here  ... let's continue  then be ready   ...  
<h2><strong>. . .</strong><h2></h2></h2> 

<h2 id="preparing-dockerfile"><strong>Preparing Dockerfile</strong></h2>
<p>Before writing the <code>dockerfile</code> take in mind that the steup should be compatible with the pipeline architecture ... if you remember we have a separate jobs for :</p>
<ul>
<li>Installing node modules </li>
<li>Run the build process </li>
</ul>
<p>So the Dockerfile must contain only the builded assets only to be served by nginx </p>
<p>Here is our sweet  Dockerfile :</p>
<pre><code class="lang-dockerfile">FROM nginx:1.16.0-alpine
COPY build/  /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d
RUN mv  /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.old
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
</code></pre>
<p>This dockerfile does not do too much work, it just take the <code>/build directory</code> and copy it under <code>/usr/share/nginx/html</code> to be served.</p>
<p>Also we need a basic nginx config like follow to be under <code>/etc/nginx/conf.d</code>: </p>
<pre><code class="lang-nginx"><span class="hljs-section">server</span> {
  <span class="hljs-attribute">include</span> mime.types;
  <span class="hljs-attribute">listen</span> <span class="hljs-number">80</span>;
  <span class="hljs-attribute">location</span> / {
    <span class="hljs-attribute">root</span>   /usr/share/nginx/html;
    <span class="hljs-attribute">index</span>  index.html index.htm;
    <span class="hljs-attribute">try_files</span> <span class="hljs-variable">$uri</span> <span class="hljs-variable">$uri</span>/ /index.html;
  }
  <span class="hljs-attribute">error_page</span>   <span class="hljs-number">500</span> <span class="hljs-number">502</span> <span class="hljs-number">503</span> <span class="hljs-number">504</span>  /50x.html;
  <span class="hljs-attribute">location</span> = /50x.html {
    <span class="hljs-attribute">root</span>   /usr/share/nginx/html;
  }
}
</code></pre>
<p>You see !  its simple let's proceed to setup the <code>ansible playbook</code> for the deployment process ... hurry up</p>
<h2><strong>. . .</strong><h2></h2></h2> 

<h2 id="deployment-with-ansible"><strong>Deployment with ansible</strong></h2>
<p>We are almost done ! the task now is to write the ansible playbook that will do the folowing :</p>
<ul>
<li>Create a docker network and specify the the gateway address</li>
<li>Authenticate the gitlab registry </li>
<li>Start the container with the suitable configurations </li>
<li>Clean the unsed containers and volumes </li>
<li>Most setup will be in the <code>inventory file</code></li>
</ul>
<p>Let's take a look at the <code>inventory_file</code>: </p>
<pre><code class="lang-toml"><span class="hljs-section">[dev]</span>
devserver <span class="hljs-attr">ansible_ssh_host</span>=dev ansible_ssh_user=root ansible_python_interpreter=/usr/bin/python

<span class="hljs-section">[dev:vars]</span>
<span class="hljs-attr">c_name</span>={{ lookup(<span class="hljs-string">'env'</span>,<span class="hljs-string">'CI_PROJECT_NAME'</span>) }}-dev <span class="hljs-comment">#container name</span>
<span class="hljs-attr">h_name</span>={{ lookup(<span class="hljs-string">'env'</span>,<span class="hljs-string">'CI_PROJECT_NAME'</span>) }}-dev <span class="hljs-comment">#host name</span>
<span class="hljs-attr">subnet</span>=<span class="hljs-number">172.30</span>.<span class="hljs-number">0</span>  <span class="hljs-comment"># network gateway                                         </span>
<span class="hljs-attr">network_name</span>=project_name_dev
<span class="hljs-attr">registry_url</span>={{ lookup(<span class="hljs-string">'env'</span>,<span class="hljs-string">'CI_REGISTRY'</span>) }}                          
<span class="hljs-attr">registry_user</span>={{ lookup(<span class="hljs-string">'env'</span>,<span class="hljs-string">'GITLAB_REGISTRY_USER'</span>) }}    
<span class="hljs-attr">registry_password</span>={{ lookup(<span class="hljs-string">'env'</span>,<span class="hljs-string">'GITLAB_REGISTRY_PASS'</span>) }}  
<span class="hljs-attr">image_name</span>={{ lookup(<span class="hljs-string">'env'</span>,<span class="hljs-string">'CI_REGISTRY_IMAGE'</span>) }}:{{ lookup(<span class="hljs-string">'env'</span>,<span class="hljs-string">'CI_COMMIT_SHORT_SHA'</span>) }}-dev 

<span class="hljs-section">[project_network:children]</span>
dev
<span class="hljs-section">[project_clean:children]</span>
dev
</code></pre>
<p>The <code>ansible_ssh_host=dev</code> refers to the <code>SSH_CFG</code> configuration.</p>
<p>Gitlab by default exports many useful environment variables like :</p>
<ul>
<li><code>CI_PROJECT_NAME</code> : the repo name </li>
<li><code>CI_COMMIT_SHORT_SHA</code> : the sha commit ID to tag the container</li>
</ul>
<p>You can explore all variables <a target="_blank" href="https://docs.gitlab.com/ee/ci/variables/">here</a>.</p>
<p>Let's move now to the playbook ... i'm tired damn it haha .. it is a long post ... okay nevermind come on .. </p>
<p>Here is the ansible playbook : </p>
<pre><code class="lang-yaml"><span class="hljs-meta">---</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">hosts:</span> <span class="hljs-string">project_network</span>
  <span class="hljs-comment">#become: yes # for previlged user</span>
  <span class="hljs-comment">#become_method: sudo   # for previlged user</span>
  <span class="hljs-attr">tasks:</span>                                                     
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Create</span> <span class="hljs-string">docker</span> <span class="hljs-string">network</span>
    <span class="hljs-attr">docker_network:</span>
      <span class="hljs-attr">name:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ network_name }}</span>"</span>
      <span class="hljs-attr">ipam_config:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">subnet:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ subnet }}</span>.0/16"</span>
          <span class="hljs-attr">gateway:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ subnet }}</span>.1"</span>

<span class="hljs-bullet">-</span> <span class="hljs-attr">hosts:</span> <span class="hljs-string">dev</span>
  <span class="hljs-attr">gather_facts:</span> <span class="hljs-literal">no</span>
  <span class="hljs-comment">#become: yes # for previlged user</span>
  <span class="hljs-comment">#become_method: sudo   # for previlged user</span>
  <span class="hljs-attr">tasks:</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Log</span> <span class="hljs-string">into</span> <span class="hljs-string">gitlab</span> <span class="hljs-string">registry</span> <span class="hljs-string">and</span> <span class="hljs-string">force</span> <span class="hljs-string">re-authorization</span>
    <span class="hljs-attr">docker_login:</span>
      <span class="hljs-attr">registry:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ registry_url }}</span>"</span>
      <span class="hljs-attr">username:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ registry_user }}</span>"</span>
      <span class="hljs-attr">password:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ registry_password }}</span>"</span>
      <span class="hljs-attr">reauthorize:</span> <span class="hljs-literal">yes</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">name :</span> <span class="hljs-string">start</span> <span class="hljs-string">the</span> <span class="hljs-string">container</span>
    <span class="hljs-attr">docker_container:</span>
      <span class="hljs-attr">name:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ c_name }}</span>"</span>
      <span class="hljs-attr">image :</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ image_name }}</span>"</span>
      <span class="hljs-attr">pull:</span> <span class="hljs-literal">yes</span>
      <span class="hljs-attr">restart_policy:</span> <span class="hljs-string">always</span>
      <span class="hljs-attr">hostname:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ h_name }}</span>"</span>
      <span class="hljs-comment"># volumes:</span>
      <span class="hljs-comment">#   - /some/path:/some/path</span>
      <span class="hljs-attr">exposed_ports:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">"80"</span>
      <span class="hljs-attr">networks:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ network_name }}</span>"</span>
          <span class="hljs-attr">ipv4_address:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ subnet }}</span>.2"</span>
      <span class="hljs-attr">purge_networks:</span> <span class="hljs-literal">yes</span>

<span class="hljs-bullet">-</span> <span class="hljs-attr">hosts :</span> <span class="hljs-string">project_clean</span>
  <span class="hljs-comment">#become: yes # for previlged user</span>
  <span class="hljs-comment">#become_method: sudo   # for previlged user</span>
  <span class="hljs-attr">gather_facts :</span> <span class="hljs-literal">no</span> 
  <span class="hljs-attr">tasks:</span> 

  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Removing</span> <span class="hljs-string">exited</span> <span class="hljs-string">containers</span>
    <span class="hljs-attr">shell:</span> <span class="hljs-string">docker</span> <span class="hljs-string">ps</span> <span class="hljs-string">-a</span> <span class="hljs-string">-q</span> <span class="hljs-string">-f</span> <span class="hljs-string">status=exited</span> <span class="hljs-string">|</span> <span class="hljs-string">xargs</span> <span class="hljs-string">--no-run-if-empty</span> <span class="hljs-string">docker</span> <span class="hljs-string">rm</span> <span class="hljs-string">--volumes</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Removing</span> <span class="hljs-string">untagged</span> <span class="hljs-string">images</span>
    <span class="hljs-attr">shell:</span> <span class="hljs-string">docker</span> <span class="hljs-string">images</span> <span class="hljs-string">|</span> <span class="hljs-string">awk</span> <span class="hljs-string">'/^&lt;none&gt;/ { print $3 }'</span> <span class="hljs-string">|</span> <span class="hljs-string">xargs</span> <span class="hljs-string">--no-run-if-empty</span> <span class="hljs-string">docker</span> <span class="hljs-string">rmi</span> <span class="hljs-string">-f</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Removing</span> <span class="hljs-string">volume</span> <span class="hljs-string">directories</span>
    <span class="hljs-attr">shell:</span> <span class="hljs-string">docker</span> <span class="hljs-string">volume</span> <span class="hljs-string">ls</span> <span class="hljs-string">-q</span> <span class="hljs-string">--filter="dangling=true"</span> <span class="hljs-string">|</span> <span class="hljs-string">xargs</span> <span class="hljs-string">--no-run-if-empty</span> <span class="hljs-string">docker</span> <span class="hljs-string">volume</span> <span class="hljs-string">rm</span>
</code></pre>
<p>This playbook is a life saver because we configure the container automatically before starting it ... no setup on the remote host ... we can deploy the same in any other servers based on linux. the container update is quite simple .. ansible will take care of stopping the container and starting new one with different tag and then clean up the shit </p>
<p>We can also make a <code>rollback</code> to the previous container by going to the previous pipeline history on gitlab and restart the lastest job <code>the deploy job</code> because we have already an existing container on the registry </p>
<p>The setup is for <code>dev</code> environment you can copy paste the two files for the <code>prod</code> &amp; <code>staging</code> environment ... </p>
<h2><strong>. . .</strong><h2></h2></h2> 

<h2 id="setting-up-the-pipeline"><strong>Setting up the Pipeline</strong></h2>
<p>The pipeline will deploy to the three environments as i mentioned on the top of this post ... </p>
<p>Here is the full pipeline code : </p>
<pre><code class="lang-yaml">
<span class="hljs-attr">variables:</span> 
  <span class="hljs-attr">DOCKER_IMAGE_PRODUCTION :</span> <span class="hljs-string">$CI_REGISTRY_IMAGE</span> 
  <span class="hljs-attr">DOCKER_IMAGE_TEST :</span> <span class="hljs-string">$CI_REGISTRY_IMAGE</span>   
  <span class="hljs-attr">DOCKER_IMAGE_DEV :</span> <span class="hljs-string">$CI_REGISTRY_IMAGE</span>


<span class="hljs-comment">#caching node_modules folder for later use  </span>
<span class="hljs-string">.example_cache:</span> <span class="hljs-meta">&amp;example_cache</span>
  <span class="hljs-attr">cache:</span>
    <span class="hljs-attr">paths:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">node_modules/</span>


<span class="hljs-attr">stages :</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">prep</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">build_dev</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">push_registry_dev</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">deploy_dev</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">build_test</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">push_registry_test</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">deploy_test</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">build_production</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">push_registry_production</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">deploy_production</span>


<span class="hljs-comment">########################################################</span>
<span class="hljs-comment">##                                                                                                                                 ##</span>
<span class="hljs-comment">##     Development: autorun after a push/merge                                               ## </span>
<span class="hljs-comment">##                                                                                                                                 ##</span>
<span class="hljs-comment">########################################################</span>

<span class="hljs-attr">install_dependencies:</span>
  <span class="hljs-attr">image:</span> <span class="hljs-string">node:12.2.0-alpine</span>
  <span class="hljs-attr">stage:</span> <span class="hljs-string">prep</span>
  <span class="hljs-string">&lt;&lt;:</span> <span class="hljs-meta">*example_cache</span>
  <span class="hljs-attr">script:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">npm</span> <span class="hljs-string">ci</span> <span class="hljs-string">--log-level=error</span> 

  <span class="hljs-attr">artifacts:</span>
    <span class="hljs-attr">paths:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">node_modules/</span>  
  <span class="hljs-attr">tags :</span>
    <span class="hljs-bullet">-</span>  <span class="hljs-string">runner_name</span> 
  <span class="hljs-attr">only:</span>
    <span class="hljs-attr">refs:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">prod_branch</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">staging_branch</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">dev_branch</span>
    <span class="hljs-attr">changes :</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"*.json"</span>

<span class="hljs-attr">build_react_dev:</span>
  <span class="hljs-attr">image:</span> <span class="hljs-string">node:12.2.0-alpine</span>
  <span class="hljs-attr">stage:</span> <span class="hljs-string">build_dev</span>
  <span class="hljs-string">&lt;&lt;:</span> <span class="hljs-meta">*example_cache</span>
  <span class="hljs-attr">variables:</span>
    <span class="hljs-attr">CI :</span> <span class="hljs-string">"false"</span>
  <span class="hljs-attr">script:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">cat</span> <span class="hljs-string">.env.dev</span> <span class="hljs-string">&gt;</span> <span class="hljs-string">.env</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">npm</span> <span class="hljs-string">run</span> <span class="hljs-string">build</span>

  <span class="hljs-attr">artifacts:</span>
    <span class="hljs-attr">paths:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">build/</span>
  <span class="hljs-attr">tags :</span>
    <span class="hljs-bullet">-</span>  <span class="hljs-string">runner_name</span> 
  <span class="hljs-attr">rules:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">if:</span> <span class="hljs-string">'$CI_PIPELINE_SOURCE != "trigger"  &amp;&amp; $CI_COMMIT_BRANCH == "dev_branch"'</span>


<span class="hljs-attr">build_image_dev:</span>
  <span class="hljs-attr">stage:</span> <span class="hljs-string">push_registry_dev</span>
  <span class="hljs-attr">image :</span> <span class="hljs-string">docker:19</span>
  <span class="hljs-attr">services:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">docker:19-dind</span>
  <span class="hljs-attr">variables:</span> 
    <span class="hljs-attr">DOCKER_HOST:</span> <span class="hljs-string">tcp://docker:2375/</span>
    <span class="hljs-attr">DOCKER_DRIVER:</span> <span class="hljs-string">overlay2</span>
    <span class="hljs-attr">DOCKER_TLS_CERTDIR:</span> <span class="hljs-string">""</span>
  <span class="hljs-attr">before_script:</span>
  <span class="hljs-comment"># docker login asks for the password to be passed through stdin for security</span>
  <span class="hljs-comment"># we use $CI_JOB_TOKEN here which is a special token provided by GitLab</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">echo</span> <span class="hljs-string">-n</span> <span class="hljs-string">$CI_JOB_TOKEN</span> <span class="hljs-string">|</span> <span class="hljs-string">docker</span> <span class="hljs-string">login</span> <span class="hljs-string">-u</span> <span class="hljs-string">gitlab-ci-token</span> <span class="hljs-string">--password-stdin</span> <span class="hljs-string">$CI_REGISTRY</span>
  <span class="hljs-attr">script:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">docker</span> <span class="hljs-string">build</span>  <span class="hljs-string">--tag</span> <span class="hljs-string">$DOCKER_IMAGE_DEV:$CI_COMMIT_SHORT_SHA-dev</span> <span class="hljs-string">.</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">docker</span> <span class="hljs-string">push</span> <span class="hljs-string">$DOCKER_IMAGE_DEV:$CI_COMMIT_SHORT_SHA-dev</span>
  <span class="hljs-attr">tags :</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">runner_name</span>
  <span class="hljs-attr">rules:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">if:</span> <span class="hljs-string">'$CI_PIPELINE_SOURCE != "trigger"  &amp;&amp; $CI_COMMIT_BRANCH == "dev_branch"'</span>


<span class="hljs-attr">deploy_dev:</span>
  <span class="hljs-attr">stage:</span> <span class="hljs-string">deploy_dev</span>
  <span class="hljs-attr">image:</span> <span class="hljs-string">willhallonline/ansible:latest</span>
  <span class="hljs-attr">script:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">cat</span> <span class="hljs-string">${SSH_CFG}</span> <span class="hljs-string">&gt;</span> <span class="hljs-string">"$CI_PROJECT_DIR/ssh.cfg"</span>               
    <span class="hljs-bullet">-</span> <span class="hljs-string">mkdir</span> <span class="hljs-string">-p</span> <span class="hljs-string">"$CI_PROJECT_DIR/keys"</span>                            
    <span class="hljs-bullet">-</span> <span class="hljs-string">cat</span> <span class="hljs-string">${ANSIBLE_KEY}</span> <span class="hljs-string">&gt;</span> <span class="hljs-string">"$CI_PROJECT_DIR/keys/keyfile"</span>      
    <span class="hljs-bullet">-</span> <span class="hljs-string">chmod</span> <span class="hljs-string">og-rwx</span> <span class="hljs-string">"$CI_PROJECT_DIR/keys/keyfile"</span>    
    <span class="hljs-bullet">-</span> <span class="hljs-string">cd</span> <span class="hljs-string">$CI_PROJECT_DIR</span> <span class="hljs-string">&amp;&amp;</span> <span class="hljs-string">ansible-playbook</span>  <span class="hljs-string">-i</span> <span class="hljs-string">deployment/inventory_dev</span> <span class="hljs-string">--ssh-extra-args="-F</span> <span class="hljs-string">$CI_PROJECT_DIR/ssh.cfg</span> <span class="hljs-string">-o</span> <span class="hljs-string">ControlMaster=auto</span> <span class="hljs-string">-o</span> <span class="hljs-string">ControlPersist=30m"</span> <span class="hljs-string">deployment/deploy_container_dev.yml</span>
  <span class="hljs-attr">after_script:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">rm</span> <span class="hljs-string">-r</span> <span class="hljs-string">"$CI_PROJECT_DIR/keys"</span> <span class="hljs-string">||</span> <span class="hljs-literal">true</span>                              
    <span class="hljs-bullet">-</span> <span class="hljs-string">rm</span> <span class="hljs-string">"$CI_PROJECT_DIR/ssh.cfg"</span> <span class="hljs-string">||</span> <span class="hljs-literal">true</span>

  <span class="hljs-attr">rules:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">if:</span> <span class="hljs-string">'$CI_PIPELINE_SOURCE != "trigger"  &amp;&amp; $CI_COMMIT_BRANCH == "branch_dev"'</span>
  <span class="hljs-attr">tags :</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">runner_name</span>

<span class="hljs-comment">########################################################</span>
<span class="hljs-comment">##                                                                                                                                 ##</span>
<span class="hljs-comment">##     pre-production: autorun after a push/merge                                            ## </span>
<span class="hljs-comment">##                                                                                                                                 ##</span>
<span class="hljs-comment">########################################################</span>

<span class="hljs-attr">build_react_test:</span>
  <span class="hljs-attr">image:</span> <span class="hljs-string">node:12.2.0-alpine</span>
  <span class="hljs-attr">stage:</span> <span class="hljs-string">build_test</span>
  <span class="hljs-string">&lt;&lt;:</span> <span class="hljs-meta">*example_cache</span>
  <span class="hljs-attr">variables:</span>
    <span class="hljs-attr">CI :</span> <span class="hljs-string">"false"</span>
  <span class="hljs-attr">script:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">cat</span> <span class="hljs-string">.env.test</span> <span class="hljs-string">&gt;</span> <span class="hljs-string">.env</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">npm</span> <span class="hljs-string">run</span> <span class="hljs-string">build</span>

  <span class="hljs-attr">artifacts:</span>
    <span class="hljs-attr">paths:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">build/</span>
  <span class="hljs-attr">tags :</span>
    <span class="hljs-bullet">-</span>  <span class="hljs-string">runner_name</span> 

  <span class="hljs-attr">rules:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">if:</span> <span class="hljs-string">'$CI_PIPELINE_SOURCE != "trigger"  &amp;&amp; $CI_COMMIT_BRANCH == "staging_branch"'</span>


<span class="hljs-attr">build_image_test:</span>
  <span class="hljs-attr">stage:</span> <span class="hljs-string">push_registry_test</span>
  <span class="hljs-attr">image :</span> <span class="hljs-string">docker:19</span>
  <span class="hljs-attr">services:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">docker:19-dind</span>
  <span class="hljs-attr">variables:</span> 
    <span class="hljs-attr">DOCKER_HOST:</span> <span class="hljs-string">tcp://docker:2375/</span>
    <span class="hljs-attr">DOCKER_DRIVER:</span> <span class="hljs-string">overlay2</span>
    <span class="hljs-attr">DOCKER_TLS_CERTDIR:</span> <span class="hljs-string">""</span>
  <span class="hljs-attr">before_script:</span>
  <span class="hljs-comment"># docker login asks for the password to be passed through stdin for security</span>
  <span class="hljs-comment"># we use $CI_JOB_TOKEN here which is a special token provided by GitLab</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">echo</span> <span class="hljs-string">-n</span> <span class="hljs-string">$CI_JOB_TOKEN</span> <span class="hljs-string">|</span> <span class="hljs-string">docker</span> <span class="hljs-string">login</span> <span class="hljs-string">-u</span> <span class="hljs-string">gitlab-ci-token</span> <span class="hljs-string">--password-stdin</span> <span class="hljs-string">$CI_REGISTRY</span>
  <span class="hljs-attr">script:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">docker</span> <span class="hljs-string">build</span>  <span class="hljs-string">--tag</span> <span class="hljs-string">$DOCKER_IMAGE_TEST:$CI_COMMIT_SHORT_SHA-test</span> <span class="hljs-string">.</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">docker</span> <span class="hljs-string">push</span> <span class="hljs-string">$DOCKER_IMAGE_TEST:$CI_COMMIT_SHORT_SHA-test</span>

  <span class="hljs-attr">rules:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">if:</span> <span class="hljs-string">'$CI_PIPELINE_SOURCE != "trigger" &amp;&amp; $CI_COMMIT_BRANCH == "staging_branch"'</span>
  <span class="hljs-attr">tags :</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">runner_name</span>



<span class="hljs-attr">deploy_test:</span>
  <span class="hljs-attr">stage:</span> <span class="hljs-string">deploy_test</span>
  <span class="hljs-attr">image:</span> <span class="hljs-string">willhallonline/ansible:latest</span>
  <span class="hljs-attr">script:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">cat</span> <span class="hljs-string">${SSH_CFG}</span> <span class="hljs-string">&gt;</span> <span class="hljs-string">"$CI_PROJECT_DIR/ssh.cfg"</span>               
    <span class="hljs-bullet">-</span> <span class="hljs-string">mkdir</span> <span class="hljs-string">-p</span> <span class="hljs-string">"$CI_PROJECT_DIR/keys"</span>                            
    <span class="hljs-bullet">-</span> <span class="hljs-string">cat</span> <span class="hljs-string">${ANSIBLE_KEY}</span> <span class="hljs-string">&gt;</span> <span class="hljs-string">"$CI_PROJECT_DIR/keys/keyfile"</span>      
    <span class="hljs-bullet">-</span> <span class="hljs-string">chmod</span> <span class="hljs-string">og-rwx</span> <span class="hljs-string">"$CI_PROJECT_DIR/keys/keyfile"</span>    
    <span class="hljs-bullet">-</span> <span class="hljs-string">cd</span> <span class="hljs-string">$CI_PROJECT_DIR</span> <span class="hljs-string">&amp;&amp;</span> <span class="hljs-string">ansible-playbook</span>  <span class="hljs-string">-i</span> <span class="hljs-string">deployment/inventory_test</span> <span class="hljs-string">--ssh-extra-args="-F</span> <span class="hljs-string">$CI_PROJECT_DIR/ssh.cfg</span> <span class="hljs-string">-o</span> <span class="hljs-string">ControlMaster=auto</span> <span class="hljs-string">-o</span> <span class="hljs-string">ControlPersist=30m"</span> <span class="hljs-string">deployment/deploy_container_test.yml</span>
  <span class="hljs-attr">after_script:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">rm</span> <span class="hljs-string">-r</span> <span class="hljs-string">"$CI_PROJECT_DIR/keys"</span> <span class="hljs-string">||</span> <span class="hljs-literal">true</span>                              
    <span class="hljs-bullet">-</span> <span class="hljs-string">rm</span> <span class="hljs-string">"$CI_PROJECT_DIR/ssh.cfg"</span> <span class="hljs-string">||</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">rules:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">if:</span> <span class="hljs-string">'$CI_PIPELINE_SOURCE != "trigger" &amp;&amp; $CI_COMMIT_BRANCH == "staging_branch"'</span>
  <span class="hljs-attr">tags :</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">runner_name</span>

<span class="hljs-comment">########################################################</span>
<span class="hljs-comment">##                                                                                                                                 ##</span>
<span class="hljs-comment">##     Production: must be deployed manually                                                    ## </span>
<span class="hljs-comment">##                                                                                                                                 ##</span>
<span class="hljs-comment">########################################################</span>

<span class="hljs-attr">build_react_production:</span>
  <span class="hljs-attr">image:</span> <span class="hljs-string">node:12.2.0-alpine</span>
  <span class="hljs-attr">stage:</span> <span class="hljs-string">build_production</span>
  <span class="hljs-string">&lt;&lt;:</span> <span class="hljs-meta">*example_cache</span>
  <span class="hljs-attr">variables:</span>
    <span class="hljs-attr">CI :</span> <span class="hljs-string">"false"</span>
  <span class="hljs-attr">script:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">cat</span> <span class="hljs-string">.env.prod</span> <span class="hljs-string">&gt;</span> <span class="hljs-string">.env</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">npm</span> <span class="hljs-string">run</span> <span class="hljs-string">build</span>

  <span class="hljs-attr">artifacts:</span>
    <span class="hljs-attr">paths:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">build/</span>
  <span class="hljs-attr">tags :</span>
    <span class="hljs-bullet">-</span>  <span class="hljs-string">runner_name</span> 
  <span class="hljs-attr">rules:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">if:</span> <span class="hljs-string">'$CI_PIPELINE_SOURCE != "trigger"  &amp;&amp; $CI_COMMIT_BRANCH == "prod_branch"'</span>
      <span class="hljs-attr">when:</span> <span class="hljs-string">manual</span>

<span class="hljs-attr">build_image_production:</span>
  <span class="hljs-attr">stage:</span> <span class="hljs-string">push_registry_production</span>
  <span class="hljs-attr">image :</span> <span class="hljs-string">docker:19</span>
  <span class="hljs-attr">services:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">docker:19-dind</span>
  <span class="hljs-attr">variables:</span> 
    <span class="hljs-attr">DOCKER_HOST:</span> <span class="hljs-string">tcp://docker:2375/</span>
    <span class="hljs-attr">DOCKER_DRIVER:</span> <span class="hljs-string">overlay2</span>
    <span class="hljs-attr">DOCKER_TLS_CERTDIR:</span> <span class="hljs-string">""</span>
  <span class="hljs-attr">before_script:</span>
  <span class="hljs-comment"># docker login asks for the password to be passed through stdin for security</span>
  <span class="hljs-comment"># we use $CI_JOB_TOKEN here which is a special token provided by GitLab</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">echo</span> <span class="hljs-string">-n</span> <span class="hljs-string">$CI_JOB_TOKEN</span> <span class="hljs-string">|</span> <span class="hljs-string">docker</span> <span class="hljs-string">login</span> <span class="hljs-string">-u</span> <span class="hljs-string">gitlab-ci-token</span> <span class="hljs-string">--password-stdin</span> <span class="hljs-string">$CI_REGISTRY</span>
  <span class="hljs-attr">script:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">docker</span> <span class="hljs-string">build</span>  <span class="hljs-string">--tag</span> <span class="hljs-string">$DOCKER_IMAGE_PRODUCTION:$CI_COMMIT_SHORT_SHA</span> <span class="hljs-string">.</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">docker</span> <span class="hljs-string">push</span> <span class="hljs-string">$DOCKER_IMAGE_PRODUCTION:$CI_COMMIT_SHORT_SHA</span>

  <span class="hljs-attr">rules:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">if:</span> <span class="hljs-string">'$CI_PIPELINE_SOURCE != "trigger"  &amp;&amp; $CI_COMMIT_BRANCH == "prod_branch"'</span>
  <span class="hljs-attr">tags :</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">runner_name</span>
  <span class="hljs-attr">needs:</span> [<span class="hljs-string">build_react_production</span>]



<span class="hljs-attr">deploy_production:</span>
  <span class="hljs-attr">stage:</span> <span class="hljs-string">deploy_production</span>
  <span class="hljs-attr">image:</span> <span class="hljs-string">willhallonline/ansible:latest</span>
  <span class="hljs-attr">script:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">cat</span> <span class="hljs-string">${SSH_CFG}</span> <span class="hljs-string">&gt;</span> <span class="hljs-string">"$CI_PROJECT_DIR/ssh.cfg"</span>               
    <span class="hljs-bullet">-</span> <span class="hljs-string">mkdir</span> <span class="hljs-string">-p</span> <span class="hljs-string">"$CI_PROJECT_DIR/keys"</span>                            
    <span class="hljs-bullet">-</span> <span class="hljs-string">cat</span> <span class="hljs-string">${ANSIBLE_KEY}</span> <span class="hljs-string">&gt;</span> <span class="hljs-string">"$CI_PROJECT_DIR/keys/keyfile"</span>      
    <span class="hljs-bullet">-</span> <span class="hljs-string">chmod</span> <span class="hljs-string">og-rwx</span> <span class="hljs-string">"$CI_PROJECT_DIR/keys/keyfile"</span>    
    <span class="hljs-bullet">-</span> <span class="hljs-string">cd</span> <span class="hljs-string">$CI_PROJECT_DIR</span> <span class="hljs-string">&amp;&amp;</span> <span class="hljs-string">ansible-playbook</span>  <span class="hljs-string">-i</span> <span class="hljs-string">deployment/inventory</span> <span class="hljs-string">--ssh-extra-args="-F</span> <span class="hljs-string">$CI_PROJECT_DIR/ssh.cfg</span> <span class="hljs-string">-o</span> <span class="hljs-string">ControlMaster=auto</span> <span class="hljs-string">-o</span> <span class="hljs-string">ControlPersist=30m"</span> <span class="hljs-string">deployment/deploy_container.yml</span>
  <span class="hljs-attr">after_script:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">rm</span> <span class="hljs-string">-r</span> <span class="hljs-string">"$CI_PROJECT_DIR/keys"</span> <span class="hljs-string">||</span> <span class="hljs-literal">true</span>                              
    <span class="hljs-bullet">-</span> <span class="hljs-string">rm</span> <span class="hljs-string">"$CI_PROJECT_DIR/ssh.cfg"</span> <span class="hljs-string">||</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">rules:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">if:</span> <span class="hljs-string">'$CI_PIPELINE_SOURCE != "trigger"  &amp;&amp; $CI_COMMIT_BRANCH == "prod_branch"'</span>
  <span class="hljs-attr">tags :</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">runner_name</span>
  <span class="hljs-attr">needs:</span> [<span class="hljs-string">build_image_production</span>]
</code></pre>
<p>Here is some notes about this pipeline:</p>
<ul>
<li><p>The pipeline is protected by default to not be started with the trigger token ( Gitlab pipeline trigger)</p>
</li>
<li><p>The <code>prep</code> stage will start if there is any modifications in any json file including the <code>package.json</code> file </p>
</li>
<li><p>The pipeline jobs runs on docker alpine image (DinD) so we need some variables to connect to the docker host by using <code>DOCKER_HOST: tcp://docker:2375/</code> and <code>DOCKER_TLS_CERTDIR: ""</code></p>
</li>
<li><p>The production deployment depends on the staging jobs to be succeeded and tested by the testing team. by default no auto deploy to prod ... it's manual !</p>
</li>
<li><p>I used some files to store application environment variables using <code>.env.dev</code> , <code>env.test</code> and <code>.env.prod</code> you can use what you want !</p>
</li>
<li><p>Make sure to use a good docker image for the job based images .. for node i always work with <code>LTS</code> versions. </p>
</li>
<li><p>Create a <code>deployment</code> folder to store the ansible playbooks and inventory files.</p>
</li>
<li><p>Create a <code>Cron Job</code> to delete the cache every three months to clean the cache on the <code>CI environment</code>.</p>
</li>
<li><p>On the target server make sure to install <code>docker</code>, <code>nginx</code>, <code>certbot</code> and <code>docker python package</code></p>
<h2><strong>. . .</strong><h2></h2></h2> 

</li>
</ul>
<h2 id="final-thoughts"><strong>Final thoughts</strong></h2>
<p>You can make this pipeline as template to deliver other kinds of projects like :</p>
<ul>
<li>Python</li>
<li>Rust </li>
<li>Node </li>
<li>Go</li>
</ul>
<p>I hope this post was helpful ! thanks for reading it was great to share this with you, if you have any problems in setting this just let me know !</p>
<h2><strong>Thanks !</strong><h2></h2></h2> 
]]></content:encoded></item></channel></rss>