<?xml version="1.0" encoding="utf-8"?><rss version="2.0"
    xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:wfw="http://wellformedweb.org/CommentAPI/"
    xmlns:dc="http://purl.org/dc/elements/1.1/"
    xmlns:atom="http://www.w3.org/2005/Atom"
    xmlns:media="http://search.yahoo.com/mrss/"
    xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
    xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
>
<channel>
  <title>Srijan Choudhary, all posts tagged: docker</title>
  <link>https://srijan.ch/feed/all/tag:docker</link>
  <lastBuildDate>Wed, 24 Feb 2021 10:30:00 +0000</lastBuildDate>
  <image>
    <url>https://srijan.ch/assets/favicon/favicon-32x32.png</url>
    <title>Srijan Choudhary, all posts tagged: docker</title>
    <link>https://srijan.ch/feed/all/tag:docker</link>
  </image>
  <sy:updatePeriod>daily</sy:updatePeriod>
  <sy:updateFrequency>1</sy:updateFrequency>
  <generator>Kirby</generator>
  <atom:link href="https://srijan.ch/feed/all.xml/tag:docker" rel="self" type="application/rss+xml" />
  <description>Srijan Choudhary&#039;s Articles and Notes Feed for tag: docker</description>
  <item>
    <title>Running docker jobs inside Jenkins running on docker</title>
    <description><![CDATA[Run Jenkins inside docker, but also use docker containers to run jobs on that Jenkins]]></description>
    <link>https://srijan.ch/docker-jobs-inside-jenkins-on-docker</link>
    <guid isPermaLink="false">60362aece749840001df438e</guid>
    <category><![CDATA[devops]]></category>
    <category><![CDATA[jenkins]]></category>
    <category><![CDATA[docker]]></category>
    <dc:creator>Srijan Choudhary</dc:creator>
    <pubDate>Wed, 24 Feb 2021 10:30:00 +0000</pubDate>
    <media:content url="https://srijan.ch/media/pages/blog/docker-jobs-inside-jenkins-on-docker/ebd7e48a64-1699621096/photo-1595546440771-84f0b521a533.jpeg" medium="image" />
    <content:encoded><![CDATA[<figure data-ratio="auto">
    <img src="https://srijan.ch/media/pages/blog/docker-jobs-inside-jenkins-on-docker/ebd7e48a64-1699621096/photo-1595546440771-84f0b521a533.jpeg" alt="Running docker jobs inside Jenkins running on docker">
  
  </figure>
<p><a href="https://www.jenkins.io/" rel="noreferrer">Jenkins</a> is a free and open source automation server, which is used to automate software building, testing, deployment, etc.</p> <p>I
 wanted to have a quick and easy way to run Jenkins inside docker, but 
also use docker containers to run jobs on the dockerized Jenkins. Using 
docker for jobs makes it easy to encode job runtime dependencies in the 
source code repo itself.</p> <p>The official document on <a href="https://www.jenkins.io/doc/book/installing/docker/" rel="noreferrer">running Jenkins in docker</a> is pretty comprehensive. But, I wanted a version using docker-compose (on Linux).</p> <p>So, I started with a basic compose file:</p><figure>
  <pre><code class="language-yaml">version: &#039;3.7&#039;
services:
  jenkins:
  	image: jenkins/jenkins:alpine
    ports:
      - 8081:8080
    container_name: jenkins
    volumes:
      - ./home:/var/jenkins_home</code></pre>
    <figcaption class="text-center">docker-compose.yml</figcaption>
  </figure>
<p>When using this ( <code>docker-compose up -d</code> ), things came up properly, but Jenkins did not have access to the docker daemon running on the host. Also, the docker cli binary is not present inside the container.</p><p>The way to achieve this was to mount the docker socket and cli binary to inside the container so that it can be accessed. So, we come to the following compose file:</p><figure>
  <pre><code class="language-yaml">version: &#039;3.7&#039;
services:
  jenkins:
    image: jenkins/jenkins:alpine
    ports:
      - 8081:8080
    container_name: jenkins
    volumes:
      - ./home:/var/jenkins_home
      - /var/run/docker.sock:/var/run/docker.sock
      - /usr/bin/docker:/usr/local/bin/docker</code></pre>
    <figcaption class="text-center">docker-compose.yml</figcaption>
  </figure>
<p>But, when trying to run <code>docker ps</code> inside the container with the above compose file, I was still getting the error: <code>Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock</code>. This is because the Jenkins container is running with the <code>jenkins</code> user, which does not have access to use that socket.</p><p>From my research, the commonly recommended ways to solve this problem were:</p><ul><li>Run the container as root user</li><li><code>chmod</code> the socket file to <code>777</code></li><li>Install <code>sudo</code> inside the container and give the <code>jenkins</code> user access to sudo without needing to enter password.</li></ul><p>A more secure way is to create the <code>docker</code> group inside the container, and add the <code>jenkins</code> user to that group. But, this requires us to build a custom image.</p> <p>Also, the group id of the <code>docker</code>
 group inside and outside the container have to be the same, so I had to
 add an extra check which deletes any existing group inside the 
container which uses the same group id, then creates the new <code>docker</code> group with the passed group id, and then adds the <code>jenkins</code> user to the <code>docker</code> group.</p> <p>So, the final <code>Dockerfile</code> is:</p><figure>
  <pre><code class="language-yaml">FROM jenkins/jenkins:alpine
ARG docker_group_id=999

USER root
RUN old_group=$(getent group $docker_group_id | cut -d: -f1) &amp;&amp; \
    ([ -z &quot;$old_group&quot; ] || delgroup &quot;$old_group&quot;) &amp;&amp; \
    addgroup -g $docker_group_id docker &amp;&amp; \
    addgroup jenkins docker

USER jenkins</code></pre>
    <figcaption class="text-center">Dockerfile</figcaption>
  </figure>
<p>And the final <code>docker-compose.yml</code> file is:</p><figure>
  <pre><code class="language-yaml">version: &#039;3.7&#039;
services:
  jenkins:
    build:
      context: .
      args:
        docker_group_id: 999
    ports:
      - 8081:8080
    container_name: jenkins
    volumes:
      - ./home:/var/jenkins_home
      - /var/run/docker.sock:/var/run/docker.sock
      - /usr/bin/docker:/usr/local/bin/docker</code></pre>
    <figcaption class="text-center">docker-compose.yml</figcaption>
  </figure>
<p>The <code>docker_group_id</code> argument can be edited in the compose file. Command to get the group id of docker:</p><figure>
  <pre><code class="language-shellsession">$ getent group docker | cut -d: -f3</code></pre>
  </figure>
<p>With the above, everything works:</p><figure>
  <pre><code class="language-shellsession">$ docker-compose up -d
Creating network &quot;jenkins_test_default&quot; with the default driver
Building jenkins
Step 1/6 : FROM jenkins/jenkins:alpine
alpine: Pulling from jenkins/jenkins
801bfaa63ef2: Pull complete
2b72e22c6786: Pull complete
8d16efe80b55: Pull complete
682cd8857a9a: Pull complete
29c6010e8988: Pull complete
fa466f5d199d: Pull complete
e047245de0ff: Pull complete
0cfb53380af7: Pull complete
c29612b1a095: Pull complete
cd7d4bd47719: Pull complete
21cd3d960a1f: Pull complete
f3962370d584: Pull complete
bd6f35a1ea17: Pull complete
bd0c271b250f: Pull complete
Digest: sha256:1c3d9a1ed55911f9b165dd122118bff5da57520effb180d36b5c19d2a0cfe645
Status: Downloaded newer image for jenkins/jenkins:alpine
 ---&gt; e14be04b79e8
Step 2/6 : ARG docker_group_id=999
 ---&gt; Running in f1922fa97177
Removing intermediate container f1922fa97177
 ---&gt; 79460069fb98
Step 3/6 : RUN echo &quot;Assuming docker group id: $docker_group_id&quot;
 ---&gt; Running in 11809f4ae767
Assuming docker group id: 999
Removing intermediate container 11809f4ae767
 ---&gt; e89b345f6c74
Step 4/6 : USER root
 ---&gt; Running in b2e311372bc9
Removing intermediate container b2e311372bc9
 ---&gt; 9d4d8c3ad5b2
Step 5/6 : RUN old_group=$(getent group $docker_group_id | cut -d: -f1) &amp;&amp;     ([ -z &quot;$old_group&quot; ] || delgroup &quot;$old_group&quot;) &amp;&amp;     addgroup -g $docker_group_id docker &amp;&amp;     addgroup jenkins docker
 ---&gt; Running in 357046a8ac49
Removing intermediate container 357046a8ac49
 ---&gt; 865b942324eb
Step 6/6 : USER jenkins
 ---&gt; Running in dbc2976f62c0
Removing intermediate container dbc2976f62c0
 ---&gt; c7e6fac0187c

Successfully built c7e6fac0187c
Successfully tagged jenkins_test_jenkins:latest
WARNING: Image for service jenkins was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Creating jenkins ... done

$ docker-compose exec jenkins docker ps
CONTAINER ID   IMAGE                  COMMAND                  CREATED          STATUS          PORTS                               NAMES
6c05ee1315e4   jenkins_test_jenkins   &quot;/sbin/tini -- /usr/&hellip;&quot;   47 seconds ago   Up 47 seconds   50000/tcp, 0.0.0.0:8081-&gt;8080/tcp   jenkins</code></pre>
  </figure>
<h2>Next Steps</h2>
<p><a href="https://www.digitalocean.com/community/tutorials/how-to-automate-jenkins-setup-with-docker-and-jenkins-configuration-as-code" rel="noreferrer">Here is an excellent guide</a>
 on how to setup Jenkins configuration as code. This will make this 
setup even better because nothing will need to be configured inside 
Jenkins manually - it will all be driven by code / files.</p>]]></content:encoded>
    <comments>https://srijan.ch/docker-jobs-inside-jenkins-on-docker#comments</comments>
    <slash:comments>0</slash:comments>
  </item><item>
    <title>Install docker and docker-compose using Ansible</title>
    <description><![CDATA[Optimized way to install docker and docker-compose using Ansible]]></description>
    <link>https://srijan.ch/install-docker-and-docker-compose-using-ansible</link>
    <guid isPermaLink="false">6030d3dab5e0920001f557cd</guid>
    <category><![CDATA[devops]]></category>
    <category><![CDATA[docker]]></category>
    <category><![CDATA[ansible]]></category>
    <dc:creator>Srijan Choudhary</dc:creator>
    <pubDate>Thu, 11 Jun 2020 14:30:00 +0000</pubDate>
    <media:content url="https://srijan.ch/media/pages/blog/install-docker-and-docker-compose-using-ansible/b62b609bf9-1699621096/photo-1584444707186-b7831c11014f.jpg" medium="image" />
    <content:encoded><![CDATA[<figure data-ratio="auto">
    <img src="https://srijan.ch/media/pages/blog/install-docker-and-docker-compose-using-ansible/b62b609bf9-1699621096/photo-1584444707186-b7831c11014f.jpg" alt="">
  
  </figure>
<p>Updated for 2023: I've updated this post with the following changes:</p><p>1. Added a top-level sample playbook<br>2. Used ansible apt_module's <a href="https://docs.ansible.com/ansible/latest/collections/ansible/builtin/apt_module.html#parameter-cache_valid_time" title="cache_time" rel="noreferrer">cache_time</a> parameter to prevent repeated apt-get updates<br>3. Install <code>docker-compose-plugin</code> using apt (provides docker compose v2)<br>4. Make installing docker compose v1 optional<br>5. Various fixes as suggested in comments<br>6. Tested against Debian 10,11,12 and Ubuntu 18.04 (bionic), 20.04 (focal), 22.04 (jammy) using Vagrant.</p><p>I've published a <a href="https://srijan.ch/testing-ansible-playbooks-using-vagrant" rel="noreferrer">new post on how I've done this testing</a>.</p><hr />
<p>I wanted a simple, but optimal (and fast) way to install 
docker and docker-compose using Ansible. I found a few ways online, but I
 was not satisfied.</p> <p>My requirements were:</p><ul><li>Support Debian and Ubuntu</li><li>Install docker and docker compose v2 using apt repositories</li><li>Prevent unnecessary <code>apt-get update</code> if it has been run recently (to make it fast)</li><li>Optionally install docker compose v1 by downloading from github releases<ul><li>But, don’t download if current version &gt;= the minimum version required</li></ul></li></ul><p>I feel trying to achieve these requirements gave me a very good idea of how powerful ansible can be.</p> <p>The final role and vars files can be seen in <a href="https://gist.github.com/srijan/2028af568459195cb9a3dae8d111e754">this gist</a>. But, I’ll go through each section below to explain what makes this better / faster.</p><h2>File structure</h2>
<figure>
  <pre><code class="language-treeview">playbook.yml
roles/
├── docker/
│    ├── defaults/
│    │   ├── main.yml
│    ├── tasks/
│    │   ├── main.yml
│    │   ├── docker_setup.yml</code></pre>
    <figcaption class="text-center">File structure</figcaption>
  </figure>
<h2>Playbook</h2>
<p>This is the top-level playbook. Any default vars mentioned below can be overridden here.</p><figure>
  <pre><code class="language-yaml">---
- hosts: all
  vars:
    - docker_compose_install_v1: true
    - docker_compose_version_v1: &quot;1.29.2&quot;
  tasks:
    - name: Docker setup
      block:
        - import_role: name=docker</code></pre>
    <figcaption class="text-center">playbook.yml</figcaption>
  </figure>
<h2>Variables</h2>
<p>First, we’ve defined some variables in <code>defaults/main.yml</code>. These will control which release channel of docker will be used and whether to install docker compose v1.</p><figure>
  <pre><code class="language-yaml">---
docker_apt_release_channel: stable
docker_apt_arch: amd64
docker_apt_repository: &quot;deb [arch={{ docker_apt_arch }}] https://download.docker.com/linux/{{ ansible_distribution | lower }} {{ ansible_distribution_release }} {{ docker_apt_release_channel }}&quot;
docker_apt_gpg_key: https://download.docker.com/linux/{{ ansible_distribution | lower }}/gpg
docker_compose_install_v1: false
docker_compose_version_v1: &quot;1.29.2&quot;</code></pre>
    <figcaption class="text-center">roles/docker/defaults/main.yml</figcaption>
  </figure>
<h2>Role main.yml</h2>
<p>The <code>tasks/main.yml</code> file imports tasks from <code>tasks/docker_setup.yml</code> and turns on <a href="https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_privilege_escalation.html#using-become" rel="noreferrer">become</a> for the whole task.</p><figure>
  <pre><code class="language-yaml">---
- import_tasks: docker_setup.yml
  become: true</code></pre>
    <figcaption class="text-center">roles/docker/tasks/main.yml</figcaption>
  </figure>
<h2>Docker Setup</h2>
<p>This task is divided into the following sections:</p><h3>Install dependencies</h3>
<figure>
  <pre><code class="language-yaml">- name: Install packages using apt
  apt:
    name: 
        - apt-transport-https
        - ca-certificates
        - curl
        - gnupg2
        - software-properties-common
    state: present
    cache_valid_time: 86400</code></pre>
  </figure>
<p>Here the <code>state: present</code> makes sure that these packages are only installed if not already installed. I've set <code>cache_valid_time</code> to 1 day so that <code>apt-get update</code> is not run if it has already run recently.</p><h3>Add docker repository</h3>
<figure>
  <pre><code class="language-yaml">- name: Add Docker GPG apt Key
  apt_key:
    url: &quot;{{ docker_apt_gpg_key }}&quot;
    state: present

- name: Add Docker Repository
  apt_repository:
    repo: &quot;{{ docker_apt_repository }}&quot;
    state: present
    update_cache: true</code></pre>
  </figure>
<p>Here, the <code>state: present</code> and <code>update_cache: true</code> make sure that the cache is only updated if this state was changed. So, <code>apt-get update</code> is not run if the docker repo is already present.</p><h3>Install and enable docker and docker compose v2</h3>
<figure>
  <pre><code class="language-yaml">- name: Install docker-ce
  apt:
    name: docker-ce
    state: present
    cache_valid_time: 86400

- name: Run and enable docker
  service:
    name: docker
    state: started
    enabled: true

- name: Install docker compose
  apt:
    name: docker-compose-plugin
    state: present
    cache_valid_time: 86400</code></pre>
  </figure>
<p>Again, due to <code>state: present</code> and <code>cache_valid_time: 86400</code>, there are no extra cache fetches if docker and docker-compose-plugin are already installed.</p><h2>Docker Compose V1 Setup</h2>
<p>WARNING: docker-compose v1 is end-of-life, please keep that in mind and only install/use it if absolutely required.</p><p>This task is wrapped in an <a href="https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_blocks.html" rel="noreferrer">ansible block</a> that checks if <code>docker_compose_install_v1</code> is true.</p><figure>
  <pre><code class="language-text">- name: Install docker-compose v1
  when:
    - docker_compose_install_v1 is defined
    - docker_compose_install_v1
  block:</code></pre>
  </figure>
<p>Inside the block, there are two sections:</p><h3>Check if docker-compose is installed and it’s version</h3>
<figure>
  <pre><code class="language-yaml">- name: Check current docker-compose version
  command: docker-compose --version
  register: docker_compose_vsn
  changed_when: false
  failed_when: false
  check_mode: no

- set_fact:
    docker_compose_current_version: &quot;{{ docker_compose_vsn.stdout | regex_search(&#039;(\\d+(\\.\\d+)+)&#039;) }}&quot;
  when:
    - docker_compose_vsn.stdout is defined</code></pre>
  </figure>
<p>The first block saves the output of <code>docker-compose --version</code> into a variable <code>docker_compose_vsn</code>. The <code>failed_when: false</code> ensures that this does not call a failure even if the command fails to execute. (See <a href="https://docs.ansible.com/ansible/latest/user_guide/playbooks_error_handling.html">error handling in ansible</a>).</p> <p>Sample output when docker-compose is installed: <code>docker-compose version 1.26.0, build d4451659</code></p> <p>The second block parses this output and extracts the version number using a regex (see <a href="https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters.html">ansible filters</a>). There is a <code>when</code> condition which causes the second block to skip execution if the first block failed (See <a href="https://docs.ansible.com/ansible/latest/user_guide/playbooks_conditionals.html">playbook conditionals</a>).</p><h3>Install or upgrade docker-compose if required</h3>
<figure>
  <pre><code class="language-yaml">- name: Install or upgrade docker-compose
  get_url: 
    url : &quot;https://github.com/docker/compose/releases/download/{{ docker_compose_version }}/docker-compose-Linux-x86_64&quot;
    dest: /usr/local/bin/docker-compose
    mode: &#039;a+x&#039;
    force: yes
  when: &gt;
    docker_compose_current_version == &quot;&quot;
    or docker_compose_current_version is version(docker_compose_version, &#039;&lt;&#039;)</code></pre>
  </figure>
<p>This just downloads the required docker-compose binary and saves it to <code>/usr/local/bin/docker-compose</code>,
 but it has a conditional that this will only be done if either 
docker-compose is not already installed, or if the installed version is 
less than the required version. To do version comparison, it uses 
ansible’s built-in <a href="https://docs.ansible.com/ansible/latest/user_guide/playbooks_tests.html#version-comparison">version comparison function</a>.</p> <p>So,
 we used a few ansible features to achieve what we wanted. I’m sure 
there are a lot of other things we can do to make this even better and 
more fool-proof. Maybe a post for another day.</p>]]></content:encoded>
    <comments>https://srijan.ch/install-docker-and-docker-compose-using-ansible#comments</comments>
    <slash:comments>10</slash:comments>
  </item></channel>
</rss>
