<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="http://0.0.0.0:4000/feed.xml" rel="self" type="application/atom+xml" /><link href="http://0.0.0.0:4000/" rel="alternate" type="text/html" /><updated>2026-10-04T18:40:19+02:00</updated><id>http://0.0.0.0:4000/feed.xml</id><title type="html">Alfonso Alba</title><subtitle>My public place in the internet.</subtitle><entry><title type="html">Push docker images to Google Container Registry… running gcloud inside a docker container</title><link href="http://0.0.0.0:4000/2021/06/10/docker-push-to-gcr-running-gcloud-inside-a-docker-container.html" rel="alternate" type="text/html" title="Push docker images to Google Container Registry… running gcloud inside a docker container" /><published>2021-06-10T01:00:00+02:00</published><updated>2021-06-10T01:00:00+02:00</updated><id>http://0.0.0.0:4000/2021/06/10/docker-push-to-gcr-running-gcloud-inside-a-docker-container</id><content type="html" xml:base="http://0.0.0.0:4000/2021/06/10/docker-push-to-gcr-running-gcloud-inside-a-docker-container.html"><![CDATA[<h2 id="tldr">TL;DR;</h2>

<p>Running the <code class="language-plaintext highlighter-rouge">gcloud</code> command from inside a docker container, we will get
an Access Token that we’ll use to push a docker image to a private bucket 
inside our Google Cloud Registry. The image will contain the site 
https://www.cursodegit.com.</p>

<h2 id="creating-the-docker-image">Creating the docker image</h2>

<p>In this post, we want to create a docker image that will be used to serve the
website <a href="www.cursodegit.com">https://www.cursodegit.com</a>. The repository for
this website can be found <a href="https://github.com/cursodegit/web">here</a>.</p>

<p>Getting into the details about how that docker image is built, is out of the 
scope of this post. I want to keep focus on how to push the image and not 
on how it’s build.</p>

<p>To build the image, we’ll just follow the README file in 
the <a href="https://github.com/cursodegit/web">public website’s repository</a>. We
run the following commands:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> git clone https://github.com/cursodegit/web.git
<span class="o">&gt;</span> make build
</code></pre></div></div>

<p>This sequence of commands will create a docker image in our local computer tagged as 
<code class="language-plaintext highlighter-rouge">eu.gcr.io/web-cursodegit-com/web</code>. We want to push that image to a private bucket
inside the Google Container Registry.</p>

<h2 id="using-gcloud-inside-docker">Using gcloud inside docker</h2>

<p>Once the image is built, we need to make our local docker client
log in into the Google Container Registry. To do that, we need to run the <code class="language-plaintext highlighter-rouge">gcloud</code> 
command provided by the Google Cloud SDK several times with different options and
sub commands.</p>

<p>The <a href="https://cloud.google.com/sdk/docs/install">recommended way of running the gcloud command</a> 
is to install the SDK locally. However, being committed to use docker as much as
possible, we will run the <code class="language-plaintext highlighter-rouge">gcloud</code> command from inside a docker container.
Fortunately, Google provides a public docker image with different versions of the SDK, 
so we will just <a href="https://cloud.google.com/sdk/docs/downloads-docker">follow the documentation</a>.</p>

<p>Since we don’t need additional components, we can just use the standard image 
from google:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> docker pull gcr.io/google.com/cloudsdktool/cloud-sdk:latest
</code></pre></div></div>

<p>Once we have downloaded the image, we will log in into Google Cloud. Run the
following command (again, we are just following the docs here):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>% docker run -ti \
    --name gcloud-config \
    gcr.io/google.com/cloudsdktool/cloud-sdk \
    gcloud auth login

Go to the following link in your browser:

    https://accounts.google.com/o/oauth2/auth?response_type=code&amp;(very long URL here)&amp;code_challenge_method=S256

Enter verification code:
</code></pre></div></div>

<p>It will open a browser window to authenticate you. If the browser window doesn’t open
automatically, we will open one ourselves and copy and paste the URL provided by the previous
command:</p>

<p><img src="/assets/images/generated/posts/2021-06-10-docker-push-to-gcr-running-gcloud-inside-a-docker-container/allow-google-cloud-sdk-1433-07a883a54.png" srcset="/assets/images/generated/posts/2021-06-10-docker-push-to-gcr-running-gcloud-inside-a-docker-container/allow-google-cloud-sdk-480-07a883a54.png 480w, /assets/images/generated/posts/2021-06-10-docker-push-to-gcr-running-gcloud-inside-a-docker-container/allow-google-cloud-sdk-600-07a883a54.png 600w, /assets/images/generated/posts/2021-06-10-docker-push-to-gcr-running-gcloud-inside-a-docker-container/allow-google-cloud-sdk-800-07a883a54.png 800w, /assets/images/generated/posts/2021-06-10-docker-push-to-gcr-running-gcloud-inside-a-docker-container/allow-google-cloud-sdk-1024-07a883a54.png 1024w, /assets/images/generated/posts/2021-06-10-docker-push-to-gcr-running-gcloud-inside-a-docker-container/allow-google-cloud-sdk-1200-07a883a54.png 1200w, /assets/images/generated/posts/2021-06-10-docker-push-to-gcr-running-gcloud-inside-a-docker-container/allow-google-cloud-sdk-1433-07a883a54.png 1433w" /></p>

<p>As you can see, we are granting access to the Google Cloud SDK to perform some actions 
in our Google Cloud account. We click on the “Allow” button. In the next screen we will see
a code:</p>

<p><img src="/assets/images/generated/posts/2021-06-10-docker-push-to-gcr-running-gcloud-inside-a-docker-container/google-cloud-sdk-code-1436-8c4b4db74.png" srcset="/assets/images/generated/posts/2021-06-10-docker-push-to-gcr-running-gcloud-inside-a-docker-container/google-cloud-sdk-code-480-8c4b4db74.png 480w, /assets/images/generated/posts/2021-06-10-docker-push-to-gcr-running-gcloud-inside-a-docker-container/google-cloud-sdk-code-600-8c4b4db74.png 600w, /assets/images/generated/posts/2021-06-10-docker-push-to-gcr-running-gcloud-inside-a-docker-container/google-cloud-sdk-code-800-8c4b4db74.png 800w, /assets/images/generated/posts/2021-06-10-docker-push-to-gcr-running-gcloud-inside-a-docker-container/google-cloud-sdk-code-1024-8c4b4db74.png 1024w, /assets/images/generated/posts/2021-06-10-docker-push-to-gcr-running-gcloud-inside-a-docker-container/google-cloud-sdk-code-1200-8c4b4db74.png 1200w, /assets/images/generated/posts/2021-06-10-docker-push-to-gcr-running-gcloud-inside-a-docker-container/google-cloud-sdk-code-1436-8c4b4db74.png 1436w" /></p>

<p>We copy and paste the code in the CLI and press enter:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>% docker run -ti --name gcloud-config gcr.io/google.com/cloudsdktool/cloud-sdk gcloud auth login
Go to the following link in your browser:

    https://accounts.google.com/o/oauth2/auth?response_type=code&amp;(very long URL here)&amp;code_challenge_method=S256

Enter verification code: 4/1AY0e-g611AKN4mWz1IOKRbCvOGLJvExJ45GLm4NKYq1TCt2VCDufBtzc_Zs

You are now logged in as [cursos@cursodegit.com].
Your current project is [None].  You can change this setting by running:
  $ gcloud config set project PROJECT_ID
</code></pre></div></div>

<p>As shown above, we should see a message telling us that we have successfully
logged in.</p>

<p>This command will also create a container named <code class="language-plaintext highlighter-rouge">gcloud-config</code> with a volume that will 
persist the authentication and configuration data we just set up. We will use this container later: 
Every <code class="language-plaintext highlighter-rouge">gcloud</code> command that we run from now on, will use this container.
For this reason, please note that we didn’t use the option <code class="language-plaintext highlighter-rouge">--rm</code>: the container <code class="language-plaintext highlighter-rouge">gcloud-config</code> should not 
be deleted after running the command.</p>

<p>To try that everything is working, we will get the list of projects:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>% docker run --rm \
  --volumes-from gcloud-config \
  gcr.io/google.com/cloudsdktool/cloud-sdk \
  gcloud projects list

PROJECT_ID          NAME                PROJECT_NUMBER
web-cursodegit-com  web-cursodegit-com  XXXXXXXXXXXX
</code></pre></div></div>

<p>As I mentioned before, we are using the authentication and configuration data that we
created above. It was stored in the container that we named as <code class="language-plaintext highlighter-rouge">gloud-config</code>, and we are accessing it using the 
option <code class="language-plaintext highlighter-rouge">--volumes-from gcloud-config</code>. With this option, we mount the volumes from the container named
<code class="language-plaintext highlighter-rouge">gcloud-config</code> into the container running the <code class="language-plaintext highlighter-rouge">gcloud</code> command.</p>

<p>Should you ever need to reset the authentication or the configuration, just remove the container <code class="language-plaintext highlighter-rouge">glcoud-config</code> and recreate it
using the command that we fun before.</p>

<h2 id="enable-the-container-registry-api">Enable the container registry API</h2>

<p>Once we are authenticated, we can start our journey. The first step will be to<br />
<a href="https://cloud.google.com/container-registry/docs/enable-service">activate the container registry API</a>. 
We will use the following command to do that:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> % docker run <span class="nt">-ti</span> <span class="nt">--rm</span> <span class="se">\</span>
    <span class="nt">--volumes-from</span> gcloud-config  
    gcr.io/google.com/cloudsdktool/cloud-sdk 
    gcloud services <span class="nb">enable </span>containerregistry.googleapis.com <span class="nt">--project</span> web-cursodegit-com

Operation <span class="s2">"operations/acf.p2-499154617149-9c239f4d-735f-4309-bf41-ec190d91a768"</span> finished successfully.
</code></pre></div></div>

<p>If this API is not enabled for this project, we will not be able to push our images later.</p>

<h2 id="authentication-to-access-the-private-registry">Authentication to access the private registry.</h2>

<p>The next thing we need to do before 
pushing our image is to log in our docker client into the google container registry. Let’s do that!</p>

<p>According to <a href="https://cloud.google.com/container-registry/docs/advanced-authentication#methods">the documentation</a>, 
the recommended way to log in into the private registry is to configure docker to use the 
<a href="https://cloud.google.com/container-registry/docs/advanced-authentication#gcloud-helper">gcloud credential helper</a>.
This is what we would do if we had installed the Google Cloud SDK directly in our computer. Since we’re running <code class="language-plaintext highlighter-rouge">gcloud</code> 
from inside a docker container, our local docker commands don’t have access to the <code class="language-plaintext highlighter-rouge">gcloud</code> command because it’s not
actually installed in our computer, and therefore, it won’t work.</p>

<p>For this reason we will use one of the alternative methods. In particular, we will use the 
<a href="https://cloud.google.com/container-registry/docs/advanced-authentication#token">Access Token method</a></p>

<p>In this method, we create a service account and use that service account to get a temporary access token
that will allow us to push and pull images from the docker registry.</p>

<p>Let’s create the service account:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> docker run <span class="nt">-ti</span> <span class="nt">--rm</span> <span class="se">\</span>
    <span class="nt">--volumes-from</span> gcloud-config  <span class="se">\</span>
    gcr.io/google.com/cloudsdktool/cloud-sdk 
    gcloud iam service-accounts create container-registry-read-write <span class="nt">--project</span> web-cursodegit-com
</code></pre></div></div>

<p>Once created, we will check that the account is there:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> docker run <span class="nt">-ti</span> <span class="nt">--rm</span> <span class="se">\</span>
    <span class="nt">--volumes-from</span> gcloud-config  
    gcr.io/google.com/cloudsdktool/cloud-sdk 
    gcloud iam service-accounts list <span class="nt">--project</span> web-cursodegit-com

DISPLAY NAME                            EMAIL                                                                     DISABLED
                                        container-registry-read-write@web-cursodegit-com.iam.gserviceaccount.com  False
Compute Engine default service account  XXXXXXXXXXXXXXXXXXXX@developer.gserviceaccount.com                        False
</code></pre></div></div>

<p>Once the account is created, we need to grant the service account with 
<a href="https://cloud.google.com/container-registry/docs/access-control#permissions">the right permissions to push and pull docker images</a>.
Looking at the permissions shown in the previous link, we need to assign the role 
<code class="language-plaintext highlighter-rouge">Storage Admin</code> to our service account, let’s do that:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>% docker run <span class="nt">-ti</span> <span class="nt">--rm</span> <span class="se">\</span>
    <span class="nt">--volumes-from</span> gcloud-config  gcr.io/google.com/cloudsdktool/cloud-sdk <span class="se">\</span>
    gcloud projects add-iam-policy-binding web-cursodegit-com <span class="se">\</span>
    <span class="nt">--member</span> <span class="s2">"serviceAccount:container-registry-read-write@web-cursodegit-com.iam.gserviceaccount.com"</span> <span class="se">\</span>
    <span class="nt">--role</span> <span class="s2">"roles/storage.admin"</span>

Updated IAM policy <span class="k">for </span>project <span class="o">[</span>web-cursodegit-com].
bindings:
- members:
  - serviceAccount:service-XXXXXXXXXXXXXX@compute-system.iam.gserviceaccount.com
  role: roles/compute.serviceAgent
- members:
  - serviceAccount:XXXXXXXXXXXX-compute@developer.gserviceaccount.com
  - serviceAccount:XXXXXXXXXXXX@cloudservices.gserviceaccount.com
  role: roles/editor
- members:
  - serviceAccount:service-XXXXXXXXXXXX@firebase-rules.iam.gserviceaccount.com
  role: roles/firebaserules.system
- members:
  - user:cursos@cursodegit.com
  role: roles/owner
- members:
  - serviceAccount:container-registry-read-write@web-cursodegit-com.iam.gserviceaccount.com
  role: roles/storage.admin
etag: <span class="nv">BwXELvWeR0c</span><span class="o">=</span>
version: 1
</code></pre></div></div>

<p>Why did we chose “Storage Admin” and not “Storage Object Admin”? The container registry stores the images and it’s layers in a bucket.
That bucket is created the first time we push the image and, therefore, we need to have the necessary permissions to
create it. The “Storage Admin” role has the permission <code class="language-plaintext highlighter-rouge">storage.buckets.create</code>, which is the one we need.</p>

<p>Once the service account is in place, we need to create a key associated with the service account:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>% docker run <span class="nt">-ti</span> <span class="nt">--rm</span> <span class="se">\</span>
    <span class="nt">--volumes-from</span> gcloud-config  
    <span class="nt">-v</span> <span class="si">$(</span><span class="nb">pwd</span><span class="si">)</span>:/workdir  
    gcr.io/google.com/cloudsdktool/cloud-sdk 
    gcloud iam service-accounts keys create /workdir/keyfile.json     
    <span class="nt">--iam-account</span> container-registry-read-write@web-cursodegit-com.iam.gserviceaccount.com 

created key <span class="o">[</span>aaabcc588a41fba3b3966ca450c78e6f89b000d0] of <span class="nb">type</span> <span class="o">[</span>json] as <span class="o">[</span>keyfile.json] <span class="k">for</span> <span class="o">[</span>container-registry-read-write@web-cursodegit-com.iam.gserviceaccount.com]
</code></pre></div></div>

<p>The command <code class="language-plaintext highlighter-rouge">gcloud iam service-accounts keys create /workdir/keyfile.json</code> stores the key in the file 
<code class="language-plaintext highlighter-rouge">/workdir/keyfile.json</code> inside the container. 
We use a bind volume (see the argument <code class="language-plaintext highlighter-rouge">-v $(pwd):/workdir</code>) to mount our local working directory in the <code class="language-plaintext highlighter-rouge">/workdir</code> folder 
inside the container running the command. Doing this, we will persist the file <code class="language-plaintext highlighter-rouge">keyfile.json</code>
in the local file system of our computer, and we will be able to use it later.</p>

<p>We need to store this key. We should encrypt it and store it in our repository, keep it in a vault or add it
as secret to our CI/CD system. We’ll need this file in the future any time we want to log in into the 
Google Container Registry.</p>

<h2 id="get-the-token-and-log-in">Get the token and log in</h2>

<p>The access Token method that we are going to user works as follows:</p>
<ul>
  <li>We authenticate into Google Cloud using the service account and the file <code class="language-plaintext highlighter-rouge">keyfile.json</code></li>
  <li>We request a token</li>
  <li>We use the token to log in our docker client into the Google Cloud Registry</li>
</ul>

<p>As I mentioned before, you will need the file <code class="language-plaintext highlighter-rouge">keyfile.json</code> every time we need to log in, se we need to find a
secure way to store it.</p>

<p><strong>Authenticate using the service account:</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>% docker run <span class="nt">-ti</span> <span class="nt">--rm</span> <span class="se">\</span>
  <span class="nt">--volumes-from</span> gcloud-config  
  <span class="nt">-v</span> <span class="si">$(</span><span class="nb">pwd</span><span class="si">)</span>:/workdir  
  gcr.io/google.com/cloudsdktool/cloud-sdk gcloud auth activate-service-account container-registry-read-write@web-cursodegit-com.iam.gserviceaccount.com 
  <span class="nt">--key-file</span><span class="o">=</span>/workdir/keyfile.json

Activated service account credentials <span class="k">for</span>: <span class="o">[</span>container-registry-read-write@web-cursodegit-com.iam.gserviceaccount.com]
</code></pre></div></div>

<p>We use the option <code class="language-plaintext highlighter-rouge">-v $(pwd):/workdir</code> to mount our local working directory (which contains the <code class="language-plaintext highlighter-rouge">keyfile.json</code> file)
inside the container’s <code class="language-plaintext highlighter-rouge">/workdir</code> directory.</p>

<p><strong>Get the access token</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> docker run <span class="nt">-ti</span> <span class="nt">--rm</span> <span class="se">\</span>
    <span class="nt">--volumes-from</span> gcloud-config  
    gcr.io/google.com/cloudsdktool/cloud-sdk gcloud auth print-access-token
ya29.c.KqYBAghugIeM...TgNAMFmLJDmOzG8A
</code></pre></div></div>

<p><strong>Log in docker into the Google Container Registry</strong></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> <span class="nb">echo</span> <span class="s2">"ya29.c.KqYBAghugIeM...TgNAMFmLJDmOzG8A"</span>  | docker login <span class="nt">-u</span> oauth2accesstoken <span class="nt">--password-stdin</span> https://eu.gcr.io
Login Succeeded
</code></pre></div></div>

<p>Note: if your docker version doesn’t support the –password-stdin option, you can run this command instead</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> docker login <span class="nt">-u</span> oauth2accesstoken <span class="nt">-p</span> <span class="s2">"ya29.c.KqYBAghugIeM...TgNAMFmLJDmOzG8A"</span> https://eu.gcr.io
Login Succeeded
</code></pre></div></div>

<h2 id="push-the-image">Push the image</h2>

<p>Finally!!! Now we should be able to push:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>% docker push eu.gcr.io/web-cursodegit-com/web
Using default tag: latest
The push refers to repository <span class="o">[</span>eu.gcr.io/web-cursodegit-com/web]
12a2db8368f0: Pushed
4689e8eca613: Layer already exists
3480549413ea: Layer already exists
3c369314e003: Layer already exists
4531e200ac8d: Layer already exists
ed3fe3f2b59f: Layer already exists
b2d5eeeaba3a: Layer already exists
latest: digest: sha256:89238ac5b9c01e77d3839abf1784c773cd7e383d4bca4146e21fdfe1fbdc7e6b size: 1780
</code></pre></div></div>

<p>That’s it. We had to run a lot of long commands to get here, but we did it!</p>

<h2 id="conclusions">Conclusions</h2>

<ul>
  <li>If we run the Google Cloud SDK inside docker containers, we cannot use the Google Credentials Helper to log in
our docker client into the Google Container Registry.</li>
  <li>We need to use a different authentication method, which requires running more commands.</li>
  <li>Also, we need to have a very clear understanding of how volumes and bind volumes work in docker to be able
to get the files in and out of the containers.</li>
  <li>
    <p>We need to type longer commands than those needed if we had installed the Google Cloud SDK locally. However, 
we could easily reduce the verbosity of the commands by creating a simple bash wrapper script called <code class="language-plaintext highlighter-rouge">docker.sh</code> 
with this code:</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env bash</span>
<span class="c"># docker.sh file</span>
docker run <span class="nt">-ti</span> <span class="nt">--rm</span> <span class="se">\</span>
<span class="nt">--volumes-from</span> gcloud-config <span class="se">\</span>
<span class="nt">-v</span> <span class="si">$(</span><span class="nb">pwd</span><span class="si">)</span>:/workdir <span class="se">\</span>
gcr.io/google.com/cloudsdktool/cloud-sdk <span class="s2">"</span><span class="nv">$@</span><span class="s2">"</span>
</code></pre></div>    </div>

    <p>Once the script is in place, we could run:</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> ./docker.sh gcloud projects list
<span class="o">&gt;</span> ./docker.sh gcloud auth activate-service-account container-registry-read-write@web-cursodegit-com.iam.gserviceaccount.com <span class="se">\</span>
    <span class="nt">--key-file</span><span class="o">=</span>/workdir/keyfile.json
<span class="o">&gt;</span> ./docker.sh gcloud auth print-access-token
</code></pre></div>    </div>

    <p>which will substantially reduce the length of the commands we need to type. We could define an alias instead of a 
shell script.</p>
  </li>
</ul>

<p>And this is all for today. I hope you find it usefull.</p>]]></content><author><name></name></author><category term="Google Cloud" /><category term="docker" /><summary type="html"><![CDATA[In this post, we use the Access Token method to log in into Google Container Registry in order to push docker images to it. Also, instead of installing the Google Cloud SDK in our computer, we will run the gcloud commands from inside a docker container.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://0.0.0.0:4000/assets/images/cards/containers_and_man_with_walkie_talkie_card.jpeg" /><media:content medium="image" url="http://0.0.0.0:4000/assets/images/cards/containers_and_man_with_walkie_talkie_card.jpeg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Git worktrees: vida más allá de nuestro área de trabajo</title><link href="http://0.0.0.0:4000/2021/06/03/s01e04-git-worktrees.html" rel="alternate" type="text/html" title="Git worktrees: vida más allá de nuestro área de trabajo" /><published>2021-06-03T15:00:00+02:00</published><updated>2021-06-03T15:00:00+02:00</updated><id>http://0.0.0.0:4000/2021/06/03/s01e04-git-worktrees</id><content type="html" xml:base="http://0.0.0.0:4000/2021/06/03/s01e04-git-worktrees.html"><![CDATA[<p>This week I gave a talk for the Madrid Git users group. In the talk, I introduced
the <code class="language-plaintext highlighter-rouge">git-worktree</code> command and showed its basic usage. This is a quick post to 
share with all of you the links and resources related to that talk.</p>

<p>The talk was in Spanish and it was live-streamed on 
<a href="https://www.twitch.tv/cursodegit">twitch.tv</a>. If you’d like
to learn more about git, you can subscribing to my channel in 
<a href="https://www.twitch.tv/cursodegit">twitch.tv</a>,
subscribe to the youtube channel of the 
<a href="https://www.youtube.com/channel/UCxrqcHQVPkwiNu_BOuZ4jdQ">Madrid Git users group</a>
or joining us in our <a href="https://www.meetup.com/Spanish-Git-Meetup/">Meetup Group</a>!</p>

<p><a target="_blank" href="https://www.youtube.com/watch?v=UxKY3b7ImOc&t=1434s"><img src="/assets/images/generated/posts/2021-06-04-git-worktree-meetup/play-git-worktree-meetup-video-1440-cb781d031.jpeg" srcset="/assets/images/generated/posts/2021-06-04-git-worktree-meetup/play-git-worktree-meetup-video-480-cb781d031.jpeg 480w, /assets/images/generated/posts/2021-06-04-git-worktree-meetup/play-git-worktree-meetup-video-600-cb781d031.jpeg 600w, /assets/images/generated/posts/2021-06-04-git-worktree-meetup/play-git-worktree-meetup-video-800-cb781d031.jpeg 800w, /assets/images/generated/posts/2021-06-04-git-worktree-meetup/play-git-worktree-meetup-video-1024-cb781d031.jpeg 1024w, /assets/images/generated/posts/2021-06-04-git-worktree-meetup/play-git-worktree-meetup-video-1200-cb781d031.jpeg 1200w, /assets/images/generated/posts/2021-06-04-git-worktree-meetup/play-git-worktree-meetup-video-1440-cb781d031.jpeg 1440w"></a></p>

<p>Links:</p>
<ul>
  <li><a href="https://www.youtube.com/watch?v=UxKY3b7ImOc&amp;t=1434s">Recorded session (YouTube)</a></li>
  <li><a href="https://git-worktrees-meetup.alfonsoalba.com/">Presentation</a></li>
  <li><a href="https://github.com/alfonso-slides/git-worktrees-vida-mas-alla-de-nuestro-area-de-trabajo">Slides repository</a></li>
  <li><a href="https://github.com/alfonso-slides/git-worktrees-demo">Demo</a></li>
</ul>]]></content><author><name></name></author><category term="Git" /><category term="Talks" /><summary type="html"><![CDATA[On June the 1st 2021 I gave a talk for the Madrid Git Users about the git-worktree command. In this post you will find the link to the recorded session, the slides and the git repository used in the demo.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://0.0.0.0:4000/assets/images/cards/git_worktrees_meetup_card.jpeg" /><media:content medium="image" url="http://0.0.0.0:4000/assets/images/cards/git_worktrees_meetup_card.jpeg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Run Ubuntu 20.04 in an external SSD drive in a MacBook Pro</title><link href="http://0.0.0.0:4000/2021/03/27/run-ubuntu-in-external-ssd-in-macbook-pro.html" rel="alternate" type="text/html" title="Run Ubuntu 20.04 in an external SSD drive in a MacBook Pro" /><published>2021-03-27T23:30:00+01:00</published><updated>2021-03-27T23:30:00+01:00</updated><id>http://0.0.0.0:4000/2021/03/27/run-ubuntu-in-external-ssd-in-macbook-pro</id><content type="html" xml:base="http://0.0.0.0:4000/2021/03/27/run-ubuntu-in-external-ssd-in-macbook-pro.html"><![CDATA[<h2 id="tldr">TL;DR;</h2>

<p>I spent some time installing Ubuntu on a MacBook Pro, only to decide to step back when I had almost everything
ready. The reasons were:</p>

<ul>
  <li>The lack of support for Bluetooth</li>
  <li>Fans not working in a way similar to how they worked in macOS, and I was concerned about how this could affect the
hardware in the long term</li>
  <li>Poor performance when using web browsers like Chrome or Brave</li>
  <li>Problems with the sound: I was not able to connect a microphone to it</li>
</ul>

<p>☹️ Bad luck… I’ll need another way to work on a Linux box. You can read the full story below.</p>

<h2 id="table-of-contents">Table of contents</h2>
<ul id="markdown-toc">
  <li><a href="#tldr" id="markdown-toc-tldr">TL;DR;</a></li>
  <li><a href="#table-of-contents" id="markdown-toc-table-of-contents">Table of contents</a></li>
  <li><a href="#hardware" id="markdown-toc-hardware">Hardware</a></li>
  <li><a href="#which-computer-should-i-use" id="markdown-toc-which-computer-should-i-use">Which computer should I use?</a></li>
  <li><a href="#choosing-a-linux-distribution" id="markdown-toc-choosing-a-linux-distribution">Choosing a Linux Distribution</a></li>
  <li><a href="#step-1-create-a-bootable-usb-stick" id="markdown-toc-step-1-create-a-bootable-usb-stick">Step 1: Create a bootable USB stick</a>    <ul>
      <li><a href="#find-out-the-device-name-of-the-usb-stick" id="markdown-toc-find-out-the-device-name-of-the-usb-stick">Find out the device name of the USB Stick</a></li>
      <li><a href="#convert-iso-to-img" id="markdown-toc-convert-iso-to-img">Convert iso to IMG</a></li>
      <li><a href="#create-the-usb-drive" id="markdown-toc-create-the-usb-drive">Create the USB Drive</a></li>
    </ul>
  </li>
  <li><a href="#step-2-install-ubuntu" id="markdown-toc-step-2-install-ubuntu">Step 2: Install Ubuntu</a></li>
  <li><a href="#step-3-preparation-for-running-the-playbook" id="markdown-toc-step-3-preparation-for-running-the-playbook">Step 3: Preparation for running the playbook</a>    <ul>
      <li><a href="#install-pip3-and-other-system-packages" id="markdown-toc-install-pip3-and-other-system-packages">Install pip3 and other system packages</a></li>
      <li><a href="#install-python-apt" id="markdown-toc-install-python-apt">Install python-apt</a></li>
      <li><a href="#install-ansible" id="markdown-toc-install-ansible">Install Ansible</a></li>
    </ul>
  </li>
  <li><a href="#stepping-back" id="markdown-toc-stepping-back">Stepping back</a></li>
  <li><a href="#conclusion" id="markdown-toc-conclusion">Conclusion</a></li>
  <li><a href="#references" id="markdown-toc-references">References</a></li>
</ul>
<hr />

<h2 id="hardware">Hardware</h2>

<p>This is what I used:</p>

<ul>
  <li>An external SSD SATA III drive to install Linux (I don’t want to mess up with my internal drive). I used a 
<a href="https://amzn.to/3vM9t2c">Kingston A400 SSD SA400S37</a> of 240 GB.</li>
  <li>A <a href="https://amzn.to/312zJqN">SATA III to USB 3.1 adapter from Startech.com</a></li>
  <li>An external USB stick to boot and run the installer</li>
</ul>

<h2 id="which-computer-should-i-use">Which computer should I use?</h2>

<p>I had two computers I could use to install Linux:</p>

<ul>
  <li>MacBook Pro 16’ from 2019</li>
  <li>MacBook Pro 13’ from 2015</li>
</ul>

<p>At first, I tried to install it in the MacBook Pro 16’ from 2019. It’s my main computer right now, and a 
very powerful one. Unfortunately, after doing some research, I found out 
<a href="https://gist.github.com/gbrow004/096f845c8fe8d03ef9009fbb87b781a4">this gist</a> which describes all the things that work
and, more important, all those that doesn’t. Not having Wi-Fi is a problem, I need to move around with the laptop 
(that’s what a laptop is meant for, anyway) so I don’t want to go from here to there with a network cable following me
around everywhere. Also, after reading the gist I came to the conclusion that making it work seemed 
like a good side-project to learn a few things, but that’s not what I’m looking for. I want a 
Linux computer up and running in as fast as possible and, this is very important, I need to be able to work on that 
computer.</p>

<p>After ruling out the MacBook Pro from 2019, I had a look at the MacBook Pro from 2015. A little research showed that 
the latest LTS Ubuntu version (20.04 at the time of writing) had support out-of-the-box for all the hardware except 
Bluetooth. I decided to give a try.</p>

<h2 id="choosing-a-linux-distribution">Choosing a Linux Distribution</h2>

<p>I made a list of candidates:</p>

<ul>
  <li>Debian testing: I loved to use Debian testing for my working computer back in 2005-2006. However, I think 
that this was going to more time and effort that what I’m willing to invest on this, so I ruled it out.</li>
  <li>Linux Mint: based on Ubuntu/Debian, robust and easy to use.</li>
  <li>Manjaro: also easy to use but based on Arch Linux. I ruled it out because I wanted a Debian based Linux distribution.</li>
  <li>The good old Ubuntu Desktop.</li>
</ul>

<p>Finally, I decided to use Ubuntu Desktop. Why? I already knew it and had support for almost all the hardware, so I 
thought it would be the most effective distribution to use.</p>

<h2 id="step-1-create-a-bootable-usb-stick">Step 1: Create a bootable USB stick</h2>

<p>To prepepare everything for the installation, I will use the MacBook Pro 13’ from 2015 running the latest version of 
macOS Big Sur.</p>

<p>Plug in the USB Stick and open the <code class="language-plaintext highlighter-rouge">Disk Utility</code> application. On the left-hand pane, look for the USB Stick drive and
follow the next steps:</p>

<ul>
  <li>On the left-hand pane, locate the drive for the USB Stick and right click on the drive</li>
  <li>Select <code class="language-plaintext highlighter-rouge">Erase</code></li>
</ul>

<p><img src="/assets/images/generated/posts/2021-03-22-install-ubuntu/disk-utility-partition-table-1-926-a0237d995.png" srcset="/assets/images/generated/posts/2021-03-22-install-ubuntu/disk-utility-partition-table-1-480-a0237d995.png 480w, /assets/images/generated/posts/2021-03-22-install-ubuntu/disk-utility-partition-table-1-600-a0237d995.png 600w, /assets/images/generated/posts/2021-03-22-install-ubuntu/disk-utility-partition-table-1-800-a0237d995.png 800w, /assets/images/generated/posts/2021-03-22-install-ubuntu/disk-utility-partition-table-1-926-a0237d995.png 926w" /></p>

<ul>
  <li>In the pop-up window select the MacOS Extended (Journaled) file system</li>
  <li>Select the <code class="language-plaintext highlighter-rouge">GGUID Partition Table</code> scheme</li>
  <li>Click on <code class="language-plaintext highlighter-rouge">Erase</code></li>
</ul>

<p><img src="/assets/images/generated/posts/2021-03-22-install-ubuntu/disk-utility-partition-table-2-926-1cd10b83e.png" srcset="/assets/images/generated/posts/2021-03-22-install-ubuntu/disk-utility-partition-table-2-480-1cd10b83e.png 480w, /assets/images/generated/posts/2021-03-22-install-ubuntu/disk-utility-partition-table-2-600-1cd10b83e.png 600w, /assets/images/generated/posts/2021-03-22-install-ubuntu/disk-utility-partition-table-2-800-1cd10b83e.png 800w, /assets/images/generated/posts/2021-03-22-install-ubuntu/disk-utility-partition-table-2-926-1cd10b83e.png 926w" /></p>

<p>This will erase the drive, create an empty file system, and create the partition table that we need to boot from the
USB drive.</p>

<p>Once we have the partition table in place, we will “burn” the Ubuntu ISO image into the USB stick. Go to the 
<a href="https://ubuntu.com/download/desktop">Ubuntu Desktop download page</a> and get the latest version of the ISO image. We will
save it as <code class="language-plaintext highlighter-rouge">~/Downloads/ubuntu.iso</code>.</p>

<p>To “burn” it, we will use the terminal and a bunch of commands.</p>

<h3 id="find-out-the-device-name-of-the-usb-stick">Find out the device name of the USB Stick</h3>

<p>Run the following command:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> diskutil list
/dev/disk0 <span class="o">(</span>internal, physical<span class="o">)</span>:
   <span class="c">#:                       TYPE NAME                    SIZE       IDENTIFIER</span>
   0:      GUID_partition_scheme                        <span class="k">*</span>500.1 GB   disk0
   1:                        EFI EFI                     209.7 MB   disk0s1
   2:                 Apple_APFS Container disk1         499.9 GB   disk0s2

/dev/disk1 <span class="o">(</span>synthesized<span class="o">)</span>:
   <span class="c">#:                       TYPE NAME                    SIZE       IDENTIFIER</span>
   0:      APFS Container Scheme -                      +499.9 GB   disk1
                                 Physical Store disk0s2
   1:                APFS Volume OWC Aura Pro SSD - Data 37.0 GB    disk1s1
   2:                APFS Volume Preboot                 24.8 MB    disk1s2
   3:                APFS Volume Recovery                525.8 MB   disk1s3
   4:                APFS Volume VM                      1.1 GB     disk1s4
   5:                APFS Volume OWC Aura Pro SSD        11.2 GB    disk1s5

/dev/disk2 <span class="o">(</span>external, physical<span class="o">)</span>:
   <span class="c">#:                       TYPE NAME                    SIZE       IDENTIFIER</span>
   0:     FDisk_partition_scheme                        <span class="k">*</span>15.5 GB    disk2
   1:               Windows_NTFS UBUNTUINSTA             15.5 GB    disk2s1
</code></pre></div></div>

<p>Looking at the output of the <code class="language-plaintext highlighter-rouge">diskutil</code> command, we can get the name of the device we are looking for: <code class="language-plaintext highlighter-rouge">/dev/disk2</code>. 
We will unmount it before we continue:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> diskutil unmountDisk /dev/disk2
Unmount of all volumes on disk2 was successful.
</code></pre></div></div>

<h3 id="convert-iso-to-img">Convert iso to IMG</h3>

<p>Before “burning” the image, we need to convert the file that we downloaded from the ISO to the IMG format. 
We will use the <code class="language-plaintext highlighter-rouge">hdiutil</code> command to do that:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> hdiutil convert <span class="nt">-format</span> UDRW <span class="nt">-o</span> ~/Downloads/ubuntu.dmg ~/Downloads/ubuntu.iso
</code></pre></div></div>

<h3 id="create-the-usb-drive">Create the USB Drive</h3>

<p>Once we have the image in the IMG format, we can “burn” it to the USB Stick using the <code class="language-plaintext highlighter-rouge">dd</code> command:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> <span class="nb">sudo dd </span><span class="k">if</span><span class="o">=</span>~/Downloads/ubuntu.dmg <span class="nv">of</span><span class="o">=</span>/dev/rdisk2 <span class="nv">bs</span><span class="o">=</span>1m
</code></pre></div></div>

<p>⚠ <strong>When we run this command, we should make sure that we are using the correct device name. Be careful, because if you
use the wrong device you might end up corrupting your system disk and loosing data.</strong> ⚠</p>

<h2 id="step-2-install-ubuntu">Step 2: Install Ubuntu</h2>

<p>Keep you USB stick plugged in and turn off the MacBook Pro. We plug in the SSD External drive in which we want to
install our Linux Desktop. Restart your computer and remember to press the <code class="language-plaintext highlighter-rouge">ALT</code> key while the computer starts. When
you are asked, select to boot from the USB device. This will boot the Ubuntu installer.</p>

<p>We will not go into the details of the installation process since we just performed a standard installation. We did nothing
fancy here. As a reminder for myself, I’ll only mention that during the installation, we selected the following options:</p>

<ul>
  <li>Download updates during the installation</li>
  <li>Install third party software for graphics and Wi-Fi hardware and additional media formats</li>
</ul>

<p>After the installation finishes, turn off the computer and unplug the USB stick. Start it, and once again<br />
remember to press the <code class="language-plaintext highlighter-rouge">ALT</code> key. When you are asked, select the external USB Drive and let Ubuntu desktop start.</p>

<p>Congratulations! We have Ubuntu running in our MacBook Pro. Let’s install all the software we need to do our job.</p>

<h2 id="step-3-preparation-for-running-the-playbook">Step 3: Preparation for running the playbook</h2>

<p>Having reached this point, we need to do two things:</p>

<ul>
  <li>Install the missing drivers so all our hardware works: FaceTime camera and the fans</li>
  <li>Install all the applications we need to do our jobs</li>
</ul>

<p>Instead of doing it manually, we created an <a href="https://www.ansible.com/">Ansible</a> playbook to do it for us. But to be 
able to run the playbook, we need to install several packages before.</p>

<h3 id="install-pip3-and-other-system-packages">Install pip3 and other system packages</h3>

<p>Install python packages <code class="language-plaintext highlighter-rouge">pip3</code> and <code class="language-plaintext highlighter-rouge">distutils-extra</code> for python3</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> <span class="nb">sudo </span>apt <span class="nb">install </span>python3-pip python3-distutils-extra  
</code></pre></div></div>

<p>Since our playbook uses <code class="language-plaintext highlighter-rouge">curl</code>, we also need to install it:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> <span class="nb">sudo </span>apt-get <span class="nb">install </span>curl
</code></pre></div></div>

<h3 id="install-python-apt">Install python-apt</h3>

<p>Our playbook uses the <code class="language-plaintext highlighter-rouge">apt</code> ansible module. This module depends on another python module called <code class="language-plaintext highlighter-rouge">python-apt</code>.
Unfortunately, this module is not kept up-to-date pypi and if you try to <code class="language-plaintext highlighter-rouge">python-apt</code> it using pip3, the installation
fails (<a href="https://github.com/python-poetry/poetry/issues/1363#issuecomment-555913200">more info in this comment in github</a>)</p>

<p>We need to install the module <code class="language-plaintext highlighter-rouge">python-apt</code> from somewhere else… Let’s do it!</p>

<ul>
  <li>Install the package <code class="language-plaintext highlighter-rouge">libapt-pkg-dev</code>: this package installs the file <code class="language-plaintext highlighter-rouge">apt-pkg/hashes.h</code> (and other files, but this is
the one we are interested in), that pip3 will need later when we install the <code class="language-plaintext highlighter-rouge">python-apt</code> module:</li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> <span class="nb">sudo </span>apt-get <span class="nb">install </span>libapt-pkg-dev
</code></pre></div></div>

<ul>
  <li>Go to <a href="https://salsa.debian.org/apt-team/python-apt">https://salsa.debian.org/apt-team/python-apt</a> and find out
what is the last version of this repository. At the time of writing, the latest version was 2.1.7</li>
  <li>Find out the URL in github to download the source code in tar.gz format. In our case, the URL was
https://salsa.debian.org/apt-team/python-apt/-/archive/2.1.7/python-apt-2.1.7.tar.gz</li>
  <li>Install the module using pip3:</li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> pip3 <span class="nb">install </span>https://salsa.debian.org/apt-team/python-apt/-/archive/2.1.7/python-apt-2.1.7.tar.gz 
</code></pre></div></div>

<h3 id="install-ansible">Install Ansible</h3>

<p>We are ready to install Ansible. We will install it inside a virtual environment, so we can have multiple Ansible versions
available in our computer. The playbook that we want to run uses ansible 2.10, so let’s go:</p>

<ul>
  <li>Install the module <code class="language-plaintext highlighter-rouge">virtualenv</code></li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> pip3 <span class="nb">install </span>virtualenv
</code></pre></div></div>

<ul>
  <li>Add <code class="language-plaintext highlighter-rouge">~/.local/bin</code> to your path</li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> <span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="nv">$PATH</span>:~/.local/bin
</code></pre></div></div>

<ul>
  <li>Add this last step to your <code class="language-plaintext highlighter-rouge">.bashrc</code> file so it will be there the next time you open a terminal</li>
  <li>Create a virtual environment called <code class="language-plaintext highlighter-rouge">ansible2.10</code></li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> virtualenv ansible2.10 
</code></pre></div></div>

<ul>
  <li>Activate the environment:</li>
</ul>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span><span class="nb">.</span> ansible2.10/bin/activate
<span class="o">(</span>ansible2.10<span class="o">)</span> <span class="o">&gt;</span> 
</code></pre></div></div>

<ul>
  <li>Install Ansible
```bash
(ansible2.10) &gt; pip3 install ansible</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>We are ready now to run the playbook!!!

## Step 4: Running the playbook

Get the playbook from [this repository](https://github.com/alfonsoalba/linux-in-macbook-pro-13-from-2015). Once downloaded, 
make sure you run the playbook using a user that can run admin commands using `sudo`. Get some popcorn and run it:
bash
</code></pre></div></div>
<p>(ansible2.10) &gt; ansible-playbook -K playbook.yml 
BECOME password:
…
```</p>

<p>Enter the password to become root and let the playbook run. It will do the following:</p>

<ul>
  <li>Install software and it’s dependencies. We need Slack, Libreoffice, Evolution, WhatsApp, ToggleTrack, etc.
The playbook will use <code class="language-plaintext highlighter-rouge">apt</code>, <code class="language-plaintext highlighter-rouge">snap</code> or <code class="language-plaintext highlighter-rouge">flatpak</code> depending on the software we want to install. You can look
at the playbook to see the source used to install each package</li>
  <li>Compile the drivers to be able to use the FaceTime camera</li>
  <li>Install <code class="language-plaintext highlighter-rouge">macfanctld</code> so the fans are turned on when the temperature is too high</li>
  <li>Install docker and docker-compose</li>
</ul>

<p>After it finishes, we can say that we are done: Congratulations! We have a fully functional working Ubuntu Desktop
running on our MacBook Pro from 2015.</p>

<h2 id="stepping-back">Stepping back</h2>

<p>Once everything is installed, we can start working. And with working I mean do my job: run Ansible playbooks,
open websites, check my email, look at my calendar, access files in the cloud, attend to video calls… 
This is what we found out when we started using the computer to do actual work:</p>

<ul>
  <li>The fans do not work in the same way they do in macOS. The module can either turn it on and off, while in macOS they
run at different speeds.</li>
  <li>I have no Bluetooth because there are no drivers available. Although not critical, this was very annoying.</li>
  <li>The microphone was not working. Sound was playing okay, but I couldn’t make the microphone work. I tried to fix it, because
I had the feeling that there was something wrong with the pulse configuration. Unfortunately I couldn’t figure it out.</li>
  <li>Opening a few tabs in the browser made the computer fans spin constantly… there was something going on with the 
hardware acceleration and maybe the drives of the graphics card.</li>
</ul>

<p>After trying to fix the issues for a few hours, I decided to step back and don’t use Linux in this computer. 
The main reason was the way the fans were working: I don’t want to damage any component of the computer due to lack of 
proper cooling. Also, the issue with the microphone was critical, because without a working microphone, I can’t attend 
to online meetings. I also tried and external USB audio card but had the same problem with it. I have the feeling that 
it’s something to do with the configuration of pulse, but I couldn’t find out what was it.</p>

<h2 id="conclusion">Conclusion</h2>

<p>I was able to install and run Ubuntu 20.04 in the MacBook Pro 13’ from 2015. The installation of all the software 
and hardware drivers was easy. It was a productive exercise for me, since it’s been a long, long time since the last time
I had to compile and install drivers for the Linux Kernel. I’ve also learned about snap and flatpak, two ways of 
distributing software for Linux that were new to me… I’m used to the good-old package managers like apt or yum.</p>

<p>Unfortunately, a few things made me step back:</p>

<ul>
  <li>not being able to configure the microphone</li>
  <li>the simple on/off setup for the fans (I don’t want to damage the computer)</li>
  <li>the low performance that I was getting from the graphics card</li>
</ul>

<p>What do I mean with step back? It means that I will not use this computer to work under the Linux operating system. 
I need to find another way 🤔 Stay tuned!</p>

<h2 id="references">References</h2>

<ul>
  <li>Non-affiliate amazon links:
    <ul>
      <li>https://www.amazon.es/gp/product/B00XLAZODE/ref=ppx_yo_dt_b_asin_title_o01_s00?ie=UTF8&amp;psc=1</li>
      <li>https://www.amazon.es/gp/product/B01N5IB20Q/ref=ppx_yo_dt_b_asin_title_o01_s00?ie=UTF8&amp;psc=1</li>
    </ul>
  </li>
  <li>https://itsfoss.com/create-bootable-ubuntu-usb-drive-mac-os/</li>
</ul>]]></content><author><name></name></author><category term="Linux" /><summary type="html"><![CDATA[I'm committed to working efficiently in the three most common operating systems: macOS, Windows 10, and Linux. I can already work with windows and macOS. In this post, I share with you the procedure that I followed to install and run Ubuntu 20.04 from an external SSD USB Drive connected to a MacBook Pro... and why I decided to step back after I had everything ready to start working.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://0.0.0.0:4000/assets/images/cards/computer_with_macos_and_ubuntu_card.jpeg" /><media:content medium="image" url="http://0.0.0.0:4000/assets/images/cards/computer_with_macos_and_ubuntu_card.jpeg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Run Windows 10 in an external SSD drive in a MacBook Pro</title><link href="http://0.0.0.0:4000/2021/01/15/run-windows-10-in-external-ssd-in-macbook-pro.html" rel="alternate" type="text/html" title="Run Windows 10 in an external SSD drive in a MacBook Pro" /><published>2021-01-15T12:00:00+01:00</published><updated>2021-01-15T12:00:00+01:00</updated><id>http://0.0.0.0:4000/2021/01/15/run-windows-10-in-external-ssd-in-macbook-pro</id><content type="html" xml:base="http://0.0.0.0:4000/2021/01/15/run-windows-10-in-external-ssd-in-macbook-pro.html"><![CDATA[<h2 id="tldr">TL;DR;</h2>

<p>There isn’t much to say here if you don’t want to read the whole article. If you follow this procedure you will be 
able to run Windows 10 from an external SSD drive. 😎</p>

<h2 id="disclaimer">Disclaimer</h2>

<p>The links in the article are Amazon affiliate links that will help me monetize the site and pay some of the costs of 
keeping it alive. At the end of the post, there is a section with links that don’t have the affiliate code in them.</p>

<h2 id="before-we-start">Before we start</h2>

<p>As I wrote in a <a href="/2021/01/15/run-windows-10-in-external-ssd-in-macbook-pro.html">previous post</a>, one of the 
things that I want to able to do is working efficiently in the three most 
common operating systems: macOS, Windows 10, and Linux. In this post, I share with you the procedure that I followed
to install and run Windows 10 from an external SSD USB Drive</p>

<p>This is all the hardware and software that I used:</p>

<ul>
  <li>A Windows 10 Pro License Key</li>
  <li>An external USB to download and save the Bootcamp Windows 10 support files. I’m using a 
<a href="https://amzn.to/3oOMQWM">Kinstong Datatraveler of 64Gb</a> but any USB stick of 4GB size will be more that enough</li>
  <li>An external SSD (I’m using a <a href="https://amzn.to/3qnbJcw">Samsung 860 Pro</a> )</li>
  <li>Virtual Box</li>
  <li>External USB keyboard (recommended) I have a very basic 
<a href="https://amzn.to/39B5Stj">Perixx Periboard-407</a><br />
that I use as an emergency keyboard. It was very handy during the installation since it saved me a few steps.</li>
  <li>External USB mouse (recommended) I have a <a href="https://amzn.to/39B5Stj">Logitech B100</a> emergency USB mouse</li>
  <li>If you have Mac with T2 Chip, we will need to loosen some security settings after the installation to be able to boot
from the external drive. We need to be fine with that.</li>
</ul>

<p>After gathering all the hardware, the first thing we need to do is download and install <a href="https://www.virtualbox.org/">Virtual Box</a>
in our mac. We can also use VMWare Fusion, Parallels but I chose Virtual Box because it’s 
<a href="https://www.virtualbox.org/wiki/Licensing_FAQ">open source and free to use</a>.</p>

<p>We will also need the Windows 10 ISO image, which can be downloaded from the 
<a href="https://www.microsoft.com/en-us/software-download/windows10ISO">Microsoft website</a>. According to the downloads page, 
it’s important that we select the same language that we will use later during the installation. We choose 
<code class="language-plaintext highlighter-rouge">English international</code> and download the 64-bit ISO file.</p>

<h2 id="format-the-external-usb">Format the external USB</h2>

<p><strong>⚠️ This step will erase all the data in your usb stick. Make a backup of any file you don’t want to loose. ⚠️</strong></p>

<p>We will use the external USB drive to copy the Bootcamp Drivers from the MAc and install them later in Windows.</p>

<p>Insert the drive and open the <code class="language-plaintext highlighter-rouge">Disk Utility</code> application. Once opened, we go to the menu <code class="language-plaintext highlighter-rouge">View -&gt; Show All Devices</code> (or
use the <code class="language-plaintext highlighter-rouge">CMD+2</code> shortcut). Once we can see all the volumes, we select the USB drive, right-click on it and select Erase
in the secondary menu:</p>

<p><img src="/assets/images/generated/posts/2021-01-18-install-windows-10/erase_usb_stick_with_mbr_step1-1440-3a2047a55.png" srcset="/assets/images/generated/posts/2021-01-18-install-windows-10/erase_usb_stick_with_mbr_step1-480-3a2047a55.png 480w, /assets/images/generated/posts/2021-01-18-install-windows-10/erase_usb_stick_with_mbr_step1-600-3a2047a55.png 600w, /assets/images/generated/posts/2021-01-18-install-windows-10/erase_usb_stick_with_mbr_step1-800-3a2047a55.png 800w, /assets/images/generated/posts/2021-01-18-install-windows-10/erase_usb_stick_with_mbr_step1-1024-3a2047a55.png 1024w, /assets/images/generated/posts/2021-01-18-install-windows-10/erase_usb_stick_with_mbr_step1-1200-3a2047a55.png 1200w, /assets/images/generated/posts/2021-01-18-install-windows-10/erase_usb_stick_with_mbr_step1-1440-3a2047a55.png 1440w" /></p>

<p>In the dialog window we select the name of for the Drive (we will use BOOTCAMP), an <code class="language-plaintext highlighter-rouge">ExFAT</code> format and a <code class="language-plaintext highlighter-rouge">Master Boot Record</code>
scheme:</p>

<p><img src="/assets/images/generated/posts/2021-01-18-install-windows-10/erase_usb_stick_with_mbr_step2-1440-17cb75f9b.png" srcset="/assets/images/generated/posts/2021-01-18-install-windows-10/erase_usb_stick_with_mbr_step2-480-17cb75f9b.png 480w, /assets/images/generated/posts/2021-01-18-install-windows-10/erase_usb_stick_with_mbr_step2-600-17cb75f9b.png 600w, /assets/images/generated/posts/2021-01-18-install-windows-10/erase_usb_stick_with_mbr_step2-800-17cb75f9b.png 800w, /assets/images/generated/posts/2021-01-18-install-windows-10/erase_usb_stick_with_mbr_step2-1024-17cb75f9b.png 1024w, /assets/images/generated/posts/2021-01-18-install-windows-10/erase_usb_stick_with_mbr_step2-1200-17cb75f9b.png 1200w, /assets/images/generated/posts/2021-01-18-install-windows-10/erase_usb_stick_with_mbr_step2-1440-17cb75f9b.png 1440w" /></p>

<p>Wait while the drive is formatted and close the <code class="language-plaintext highlighter-rouge">Disk Utility</code> application once it’s done.</p>

<h2 id="boot-camp-windows-support-software">Boot Camp Windows Support Software</h2>

<p>Once the USB stick has been formatted, we will copy the Windows Support Software to it.</p>

<p>Open the <code class="language-plaintext highlighter-rouge">Boot Camp Assistant</code> application and select <code class="language-plaintext highlighter-rouge">Action -&gt; Download Windows Support Software</code>. This will open the
following dialog window in which we will select the BOOTCAMP drive:</p>

<p><img src="/assets/images/generated/posts/2021-01-18-install-windows-10/bootcamp_assistant_support_software_download-668-b3b4f4126.png" srcset="/assets/images/generated/posts/2021-01-18-install-windows-10/bootcamp_assistant_support_software_download-480-b3b4f4126.png 480w, /assets/images/generated/posts/2021-01-18-install-windows-10/bootcamp_assistant_support_software_download-600-b3b4f4126.png 600w, /assets/images/generated/posts/2021-01-18-install-windows-10/bootcamp_assistant_support_software_download-668-b3b4f4126.png 668w" /></p>

<p>It can take a few minutes (12 minutes in our case) for the software to be downloaded and saved into the USB stick, so 
have a little patience.</p>

<p>There is an Apple support article describing this process <a href="https://support.apple.com/en-us/HT204923">here</a>.</p>

<h2 id="format-the-ssd-drive">Format the SSD drive</h2>

<p>Plug the SSD drive, open the <code class="language-plaintext highlighter-rouge">Disk Utility</code> application again and erase the drive using the following parameters:</p>

<p><img src="/assets/images/generated/posts/2021-01-18-install-windows-10/prepare_ssd-1440-9903fc187.png" srcset="/assets/images/generated/posts/2021-01-18-install-windows-10/prepare_ssd-480-9903fc187.png 480w, /assets/images/generated/posts/2021-01-18-install-windows-10/prepare_ssd-600-9903fc187.png 600w, /assets/images/generated/posts/2021-01-18-install-windows-10/prepare_ssd-800-9903fc187.png 800w, /assets/images/generated/posts/2021-01-18-install-windows-10/prepare_ssd-1024-9903fc187.png 1024w, /assets/images/generated/posts/2021-01-18-install-windows-10/prepare_ssd-1200-9903fc187.png 1200w, /assets/images/generated/posts/2021-01-18-install-windows-10/prepare_ssd-1440-9903fc187.png 1440w" /></p>

<h2 id="prepare-virtual-box-to-install-windows">Prepare Virtual Box to install Windows</h2>

<p>You might be tempted to burn the iso image to a DVD, boot from it, and install it. Unfortunately, that will not
work. The installation program will complain that it cannot be installed into a USB drive and you will not be able do
it this way.</p>

<p>We need to trick the Windows installation program into thinking that our USB external drive is an internal one. We will
use Virtual Box to do the trick.</p>

<p>We plug in our external SSD drive and run the command <code class="language-plaintext highlighter-rouge">diskutil list</code>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>% diskutil list
/dev/disk0 <span class="o">(</span>internal, physical<span class="o">)</span>:
   <span class="c">#:                       TYPE NAME                    SIZE       IDENTIFIER</span>
   0:      GUID_partition_scheme                        <span class="k">*</span>251.0 GB   disk0
   1:                        EFI ⁨EFI⁩                     209.7 MB   disk0s1
   2:                 Apple_APFS ⁨Container disk1⁩         250.8 GB   disk0s2

/dev/disk1 <span class="o">(</span>synthesized<span class="o">)</span>:
   <span class="c">#:                       TYPE NAME                    SIZE       IDENTIFIER</span>
   0:      APFS Container Scheme -                      +250.8 GB   disk1
                                 Physical Store disk0s2
   1:                APFS Volume ⁨Macintosh HD - Data⁩     67.3 GB    disk1s1
   2:                APFS Volume ⁨Preboot⁩                 303.6 MB   disk1s2
   3:                APFS Volume ⁨Recovery⁩                1.2 GB     disk1s3
   4:                APFS Volume ⁨VM⁩                      2.1 GB     disk1s4
   5:                APFS Volume ⁨Macintosh HD - Data⁩     35.4 GB    disk1s5
   6:                APFS Volume ⁨Macintosh HD⁩            14.9 GB    disk1s6
   7:              APFS Snapshot ⁨com.apple.os.update-...⁩ 14.9 GB    disk1s6s1

/dev/disk2 <span class="o">(</span>external, physical<span class="o">)</span>:
   <span class="c">#:                       TYPE NAME                    SIZE       IDENTIFIER</span>
   0:      GUID_partition_scheme                        <span class="k">*</span>256.1 GB   disk2
   1:                        EFI ⁨EFI⁩                     209.7 MB   disk2s1
   2:       Microsoft Basic Data ⁨WIN10TRAINI⁩             255.8 GB   disk2s2
</code></pre></div></div>

<p>Find our external disk, which was <code class="language-plaintext highlighter-rouge">/dev/disk2</code>. Make sure that the drive is plugged in but no volume is mounted. 
Otherwise, the next command will fail.</p>

<p>We create a <code class="language-plaintext highlighter-rouge">.vmdk</code> file for Virtual Box:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> <span class="nb">sudo </span>VBoxManage internalcommands createrawvmdk <span class="nt">-filename</span> win10training.vmdk <span class="nt">-rawdisk</span> /dev/disk2
RAW host disk VMDK file win10training.vmdk created successfully.
</code></pre></div></div>

<p>The command above will create a drive reference for Virtual Box that is pointing to the SSD in which we want to install Windows.
Remember: This command will fail if any volume in the drive is mounted.</p>

<p>Once the command is done, we will end up with a file win10training.vmdk that will be located in our home directory. It’s very 
likely that after running the command, the volume in the file will be mounted, so we have to unmount it again.</p>

<p>Once the <code class="language-plaintext highlighter-rouge">vmdk</code> file it’s in place, we create a virtual machine to install windows 10. Open virtual box with admin rights:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> <span class="nb">sudo</span> /Applications/VirtualBox.app/Contents/MacOS/VirtualBox
</code></pre></div></div>

<p>Go to Machine -&gt; New (or press CMD+N) and click the <code class="language-plaintext highlighter-rouge">Expert mode</code> in the dialog window that pops up:</p>

<p><img src="/assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_expert_mode_step1-1440-fab4793a0.png" srcset="/assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_expert_mode_step1-480-fab4793a0.png 480w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_expert_mode_step1-600-fab4793a0.png 600w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_expert_mode_step1-800-fab4793a0.png 800w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_expert_mode_step1-1024-fab4793a0.png 1024w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_expert_mode_step1-1200-fab4793a0.png 1200w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_expert_mode_step1-1440-fab4793a0.png 1440w" /></p>

<p>In the bottom of that screen, select the option “Use an existing virtual hard disk file” and click on the icon on the 
right:</p>

<p><img src="/assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_expert_mode_click_icon_to_select_vmdk_file-1440-46d3b2088.png" srcset="/assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_expert_mode_click_icon_to_select_vmdk_file-480-46d3b2088.png 480w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_expert_mode_click_icon_to_select_vmdk_file-600-46d3b2088.png 600w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_expert_mode_click_icon_to_select_vmdk_file-800-46d3b2088.png 800w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_expert_mode_click_icon_to_select_vmdk_file-1024-46d3b2088.png 1024w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_expert_mode_click_icon_to_select_vmdk_file-1200-46d3b2088.png 1200w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_expert_mode_click_icon_to_select_vmdk_file-1440-46d3b2088.png 1440w" /></p>

<p>This will open a new dialog window. We click on the <code class="language-plaintext highlighter-rouge">Add</code> icon on the top left:
<img src="/assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_dialog_to_add_the_vmdk_file-1440-ba697415f.png" srcset="/assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_dialog_to_add_the_vmdk_file-480-ba697415f.png 480w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_dialog_to_add_the_vmdk_file-600-ba697415f.png 600w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_dialog_to_add_the_vmdk_file-800-ba697415f.png 800w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_dialog_to_add_the_vmdk_file-1024-ba697415f.png 1024w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_dialog_to_add_the_vmdk_file-1200-ba697415f.png 1200w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_dialog_to_add_the_vmdk_file-1440-ba697415f.png 1440w" /></p>

<p>We browse and select the file <code class="language-plaintext highlighter-rouge">win10training.vmdk</code> that we created before:</p>

<p><img src="/assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_expert_mode_add_dialog_with_vmdk_selected-1440-3c78a4c6c.png" srcset="/assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_expert_mode_add_dialog_with_vmdk_selected-480-3c78a4c6c.png 480w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_expert_mode_add_dialog_with_vmdk_selected-600-3c78a4c6c.png 600w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_expert_mode_add_dialog_with_vmdk_selected-800-3c78a4c6c.png 800w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_expert_mode_add_dialog_with_vmdk_selected-1024-3c78a4c6c.png 1024w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_expert_mode_add_dialog_with_vmdk_selected-1200-3c78a4c6c.png 1200w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_expert_mode_add_dialog_with_vmdk_selected-1440-3c78a4c6c.png 1440w" /></p>

<p>We select the <code class="language-plaintext highlighter-rouge">win10trainig.vmdk</code> file and click on choose to close the window. We will be back to the Expert mode window, in which
we will enter a name for the virtual machine (Windows 10 Installation) and we will make sure that the machine
will have enough RAM to run the installer. After that, we can click on create:</p>

<p><img src="/assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_expert_mode_ready-1440-77ef62eaf.png" srcset="/assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_expert_mode_ready-480-77ef62eaf.png 480w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_expert_mode_ready-600-77ef62eaf.png 600w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_expert_mode_ready-800-77ef62eaf.png 800w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_expert_mode_ready-1024-77ef62eaf.png 1024w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_expert_mode_ready-1200-77ef62eaf.png 1200w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_expert_mode_ready-1440-77ef62eaf.png 1440w" /></p>

<p>We are almost there. The next step is to attach the iso image to the virtual machine. We select the machine on the left 
and go to general settings:</p>

<p><img src="/assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_click_on_general_settings-1440-8171c2e02.png" srcset="/assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_click_on_general_settings-480-8171c2e02.png 480w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_click_on_general_settings-600-8171c2e02.png 600w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_click_on_general_settings-800-8171c2e02.png 800w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_click_on_general_settings-1024-8171c2e02.png 1024w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_click_on_general_settings-1200-8171c2e02.png 1200w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_click_on_general_settings-1440-8171c2e02.png 1440w" /></p>

<p>In the settings window, we go to storage and select the empty CD on the left and click on the small CD icon to the right 
of the “Optical Drive” option. Then select the option  “Choose a disk file”</p>

<p><img src="/assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_select_iso-1440-a25438d07.png" srcset="/assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_select_iso-480-a25438d07.png 480w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_select_iso-600-a25438d07.png 600w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_select_iso-800-a25438d07.png 800w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_select_iso-1024-a25438d07.png 1024w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_select_iso-1200-a25438d07.png 1200w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_select_iso-1440-a25438d07.png 1440w" /></p>

<p>We select the Windows 10 ISO file that we downloaded before. Without closing the settings window, we click on the “System”
icon and activate the option “Enable EFI”. Close the window.</p>

<p><img src="/assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_efi_enabled-1440-c6624286e.png" srcset="/assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_efi_enabled-480-c6624286e.png 480w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_efi_enabled-600-c6624286e.png 600w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_efi_enabled-800-c6624286e.png 800w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_efi_enabled-1024-c6624286e.png 1024w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_efi_enabled-1200-c6624286e.png 1200w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_efi_enabled-1440-c6624286e.png 1440w" /></p>

<p>We are ready to install Windows 10!</p>

<h2 id="optional-step">Optional step</h2>

<p>Since the Windows 10 ISO doesn’t have the drivers for the keyboard, the trackpad, the graphics card, and the wifi 
network device, none of them will work during installation. If you have an external USB keyboard and mouse, you can
skip this step and continue to the installation. If not, you can use the procedure describe in 
<a href="https://transferx23.medium.com/adding-custom-drivers-to-a-windows-10-iso-boot-image-example-integrate-apple-bootcamp-drivers-9ed4f052fbad">this post</a>
to include all the drivers in the ISO image so you will have them available during the installation.</p>

<h2 id="install-windows-10">Install Windows 10</h2>

<p>It’s time to start the installation of Windows 10. Power on the virtual machine and start the installation process. 
After you power on the virtual machine, we will need to press a key to boot from the virtual CD drive! Remember to
do it or we will end up in a terminal shell and will need to reboot.</p>

<p><img src="/assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_windows_installer_running-1181-3597032bf.png" srcset="/assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_windows_installer_running-480-3597032bf.png 480w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_windows_installer_running-600-3597032bf.png 600w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_windows_installer_running-800-3597032bf.png 800w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_windows_installer_running-1024-3597032bf.png 1024w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_windows_installer_running-1181-3597032bf.png 1181w" /></p>

<p>We select English as the language and then we select the custom installation. When you are asked about the partition, we
kept the EFI partition, removed the FAT partition that we created before and create a new partition using the installer.</p>

<p>After doing that, continue answering the different questions of the installer until the installation begins:</p>

<p><img src="/assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_windows_installing-1287-dfb6cacdb.png" srcset="/assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_windows_installing-480-dfb6cacdb.png 480w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_windows_installing-600-dfb6cacdb.png 600w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_windows_installing-800-dfb6cacdb.png 800w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_windows_installing-1024-dfb6cacdb.png 1024w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_windows_installing-1200-dfb6cacdb.png 1200w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_windows_installing-1287-dfb6cacdb.png 1287w" /></p>

<p>Continue the configuration and let the installer install windows. <strong>⚠️ But do not let the installer restart, when the 
installer is about to restart windows you should stop 
the virtual machine. You have ten seconds before it restarts after the installation.⚠️</strong></p>

<p><img src="/assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_do_not_restart-1287-2d3d66710.png" srcset="/assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_do_not_restart-480-2d3d66710.png 480w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_do_not_restart-600-2d3d66710.png 600w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_do_not_restart-800-2d3d66710.png 800w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_do_not_restart-1024-2d3d66710.png 1024w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_do_not_restart-1200-2d3d66710.png 1200w, /assets/images/generated/posts/2021-01-18-install-windows-10/virtual_box_do_not_restart-1287-2d3d66710.png 1287w" /></p>

<p>If you miss that and the virtual machine restarts, you will have to do it again.</p>

<h2 id="configuration-to-boot-from-the-external-drive">Configuration to boot from the external drive</h2>

<p>After canceling the installer and preventing it to restart, we need to restart our machine to boot from the 
external drive. The steps to follow depend on our hardware.</p>

<h3 id="macs-without-the-t2-chip">Macs without the T2 Chip</h3>

<p>If your mac doesn’t have a T2 Chip, we can just keep pressed the ALT key 
while the computer restarts. After a few seconds, it will ask us which device we want to use. We select the <code class="language-plaintext highlighter-rouge">EFI Boot</code>
device and Windows will continue with the installation and configuration.</p>

<h3 id="macs-with-the-t2chip">Macs with the T2Chip</h3>

<p>As I told you before, this is the moment in which you need to loosen the security restrictions a little bit. In our case,
we have a MacBook Pro 16’ 2019 and we were fine with it.</p>

<p>Restart and press CMD+R to start the macOS Recovery application. You might be requested to log in using a user from
the local computer. Select a user with administration permissions and continue:</p>

<p><img src="/assets/images/generated/posts/2021-01-18-install-windows-10/start_recovery_application-1440-e5141fc3d.jpg" srcset="/assets/images/generated/posts/2021-01-18-install-windows-10/start_recovery_application-480-e5141fc3d.jpg 480w, /assets/images/generated/posts/2021-01-18-install-windows-10/start_recovery_application-600-e5141fc3d.jpg 600w, /assets/images/generated/posts/2021-01-18-install-windows-10/start_recovery_application-800-e5141fc3d.jpg 800w, /assets/images/generated/posts/2021-01-18-install-windows-10/start_recovery_application-1024-e5141fc3d.jpg 1024w, /assets/images/generated/posts/2021-01-18-install-windows-10/start_recovery_application-1200-e5141fc3d.jpg 1200w, /assets/images/generated/posts/2021-01-18-install-windows-10/start_recovery_application-1440-e5141fc3d.jpg 1440w" /></p>

<p>After logging in, we go to <code class="language-plaintext highlighter-rouge">Utilities -&gt; Startup Security Utility</code>. We will be prompted to enter an administrator password
to proceed. This is the configuration that we are using to boot from the external drive:</p>

<ul>
  <li>Turn on firmware password protection, so when somebody tries to boot from an external drive it will be prompted to enter
a password. This is the only security that we will have to avoid booting from other drives.</li>
  <li>In the section <code class="language-plaintext highlighter-rouge">Secure Boot</code> we selected the option “No security”, since Windows 10 is not an operating system trusted by apple.</li>
  <li>In the section <code class="language-plaintext highlighter-rouge">Allowed Boot Media</code> we selected the option to allow booting from removable media.</li>
</ul>

<p><img src="/assets/images/generated/posts/2021-01-18-install-windows-10/startup_security_options-1440-bce79376f.jpg" srcset="/assets/images/generated/posts/2021-01-18-install-windows-10/startup_security_options-480-bce79376f.jpg 480w, /assets/images/generated/posts/2021-01-18-install-windows-10/startup_security_options-600-bce79376f.jpg 600w, /assets/images/generated/posts/2021-01-18-install-windows-10/startup_security_options-800-bce79376f.jpg 800w, /assets/images/generated/posts/2021-01-18-install-windows-10/startup_security_options-1024-bce79376f.jpg 1024w, /assets/images/generated/posts/2021-01-18-install-windows-10/startup_security_options-1200-bce79376f.jpg 1200w, /assets/images/generated/posts/2021-01-18-install-windows-10/startup_security_options-1440-bce79376f.jpg 1440w" /></p>

<p>After setting this configuration, we close the macOS Recovery application and restart the computer while pressing the ALT key. 
Since we activated the Firmware protection password, we get a prompt to enter it:</p>

<p><img src="/assets/images/generated/posts/2021-01-18-install-windows-10/boot_locked-1440-09974243f.jpg" srcset="/assets/images/generated/posts/2021-01-18-install-windows-10/boot_locked-480-09974243f.jpg 480w, /assets/images/generated/posts/2021-01-18-install-windows-10/boot_locked-600-09974243f.jpg 600w, /assets/images/generated/posts/2021-01-18-install-windows-10/boot_locked-800-09974243f.jpg 800w, /assets/images/generated/posts/2021-01-18-install-windows-10/boot_locked-1024-09974243f.jpg 1024w, /assets/images/generated/posts/2021-01-18-install-windows-10/boot_locked-1200-09974243f.jpg 1200w, /assets/images/generated/posts/2021-01-18-install-windows-10/boot_locked-1440-09974243f.jpg 1440w" /></p>

<p><strong>⚠️ Be aware that when you enter the password in this screen, the keyboard will be configured to be en english, so if the firmware 
password that you entered has especial characters like puntuation symbols or accents, you will have to type them
as if you had a keyboard with an english layout ⚠️</strong>. Our mac has a Spanish keyboard and the firmware password contains some
puntuation symbols; it took us a few long minutes to realize this before we could unlock the screen again.</p>

<p>After unlocking the screen, we will be able to select the <code class="language-plaintext highlighter-rouge">EFI Boot</code> device and the installation of windows will
continue.</p>

<p><img src="/assets/images/generated/posts/2021-01-18-install-windows-10/select_efi_boot-1440-052f6995c.jpg" srcset="/assets/images/generated/posts/2021-01-18-install-windows-10/select_efi_boot-480-052f6995c.jpg 480w, /assets/images/generated/posts/2021-01-18-install-windows-10/select_efi_boot-600-052f6995c.jpg 600w, /assets/images/generated/posts/2021-01-18-install-windows-10/select_efi_boot-800-052f6995c.jpg 800w, /assets/images/generated/posts/2021-01-18-install-windows-10/select_efi_boot-1024-052f6995c.jpg 1024w, /assets/images/generated/posts/2021-01-18-install-windows-10/select_efi_boot-1200-052f6995c.jpg 1200w, /assets/images/generated/posts/2021-01-18-install-windows-10/select_efi_boot-1440-052f6995c.jpg 1440w" /></p>

<h2 id="boot-from-the-drive-and-finish-the-installation">Boot from the drive and finish the installation</h2>

<p>Before booting from the Windows drive, we insert the external keyboard and the mouse because we didn’t include the 
drivers in the ISO image (see above). The drivers for the keyboard, the mouse and the rest of the hardware will be 
installed later when we install bootcamp in windows, so we can use the external USB devices while we finish the 
installation.</p>

<p>After the computer restarts and you select to boot from the
external drive (see above), Windows will resume the installation and configuration. Just follow the steps of the wizard. 
During this process, windows will restart a couple of times so we had to keep an eye on the laptop to press the ALT key every
time the computer rebooted.</p>

<h2 id="install-bootcamp">Install bootcamp</h2>

<p>Once the installation of Windows is done, boot your computer from the external drive into Windows and insert the USB stick. 
Open a Windows Explorer window and navigate to <code class="language-plaintext highlighter-rouge">WindowsSupport -&gt; BootCamp</code> inside the new USB device. Run the 
<code class="language-plaintext highlighter-rouge">Setup.exe</code> program. After it’s done (it took about 5 minutes to install all everything), we will have to reboot the computer.</p>

<p>After restarting, we check the following:</p>
<ul>
  <li>keyboard: display brightness, sound, backlights… everything should work</li>
  <li>trackpad: well, our mouse moved as expected</li>
  <li>we were able to connect to our Wifi network</li>
  <li>card reader could read SD cards</li>
  <li>we could change the display settings</li>
</ul>

<h2 id="cleanup">Cleanup</h2>

<p>After you are done with windows, we restarted our computer and went back to our macOS to do some cleanup:</p>
<ul>
  <li>Open virtual box as root and remove the virtual machine</li>
  <li>Remove the .vmdk file.</li>
  <li>Reformat the USB stick using NTFS file systems so we can use it for whatever other things we need</li>
</ul>

<p>And we are ready to go! Enjoy running you Windows in your Mac!</p>

<h2 id="links">Links</h2>

<p>The following links don’t include my Amazon affiliate ID so I won’t receive any comission if you buy any of the items:</p>

<ul>
  <li><a href="https://www.amazon.es/dp/B078WQJXND/ref=twister_B078YVZDQK?_encoding=UTF8&amp;psc=1">Samsung 860 Pro</a></li>
  <li><a href="https://www.amazon.es/Kingston-DTIG4-64GB-Memoria-USB/dp/B00G9WHMYK">Kingston Datatraveler 64G</a></li>
  <li><a href="https://www.amazon.es/Perixx-PERIBOARD-409-Teclado-Ingl%C3%A9s-Espa%C3%B1ol/dp/B017DNQ410?th=1">Perixx Periboard-407 Mini-Keyboard</a></li>
  <li><a href="https://www.amazon.es/Logitech-B100-Rat%C3%B3n-%C3%B3ptico-blanco/dp/B00C1CVMSY">Logitech B100</a></li>
</ul>

<p>Other posts that I found interesting while working on this:</p>

<ul>
  <li><a href="https://medium.com/@transferx23/install-windows-10-on-an-external-hdd-or-ssd-with-boot-camp-under-macos-catalina-10-15-e0e315f2d48d">https://medium.com/@transferx23/install-windows-10-on-an-external-hdd-or-ssd-with-boot-camp-under-macos-catalina-10-15-e0e315f2d48d
</a></li>
  <li><a href="https://www.youtube.com/watch?v=sfcNR2l7ios">https://www.youtube.com/watch?v=sfcNR2l7ios</a></li>
</ul>]]></content><author><name></name></author><category term="Windows 10" /><summary type="html"><![CDATA[As I wrote in a previous post, one of the things that I want to able to do is working efficiently in the three most common operating systems: macOS, Windows 10, and Linux. In this post, I share with you the procedure that I followed to install and run Windows 10 from an external SSD USB Drive]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://0.0.0.0:4000/assets/images/cards/computer_with_macos_and_windows10_card.jpeg" /><media:content medium="image" url="http://0.0.0.0:4000/assets/images/cards/computer_with_macos_and_windows10_card.jpeg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">My goals for 2021</title><link href="http://0.0.0.0:4000/2021/01/02/my-goals-for-2021.html" rel="alternate" type="text/html" title="My goals for 2021" /><published>2021-01-02T12:00:00+01:00</published><updated>2021-01-02T12:00:00+01:00</updated><id>http://0.0.0.0:4000/2021/01/02/my-goals-for-2021</id><content type="html" xml:base="http://0.0.0.0:4000/2021/01/02/my-goals-for-2021.html"><![CDATA[<h2 id="tldr">TL;DR</h2>

<p>…There is no TL;DR for this post. You will have to read it this time if you are interested 🙃</p>

<h2 id="2021">2021</h2>

<p>This will be the first time that I write about my goals for a new year. David Bonilla has 
inspired me to do it after reading his posts with new objectives for the last years. Even though I set goals every Christmas,
I usually forget about them around February. I’m sure that writing them down and sharing them here
will help me to revisit them often and keep my focus on what I want to achieve.</p>

<h2 id="personal-goals">Personal goals</h2>

<p><strong>Diet</strong> (<em>high priority</em>). In 2018 I was diagnosed with an intolerance to sorbitol. I was given a diet that excluded 
any kind of fruit. Also, I could only eat like 7 kinds of vegetables. It is a very restrictive diet. The doctor who prescribed me 
the diet is an excellent professional and I really trust him. He did a good job diagnosing the intolerance but 
I’m not sure that following such a restrictive diet for the rest of my life it’s a good idea. One of my goals for 
2021 is to go to a nutritionist and tailor an appropriate diet for me.</p>

<p><a href="https://en.wikipedia.org/wiki/Stoicism"><strong>Estoicism</strong> a is a school of Hellenistic philosophy founded by Zeno of Citium in Athens in the early 3rd century BC</a>. 
One of its principles is to focus and act on what depends on you and accept what doesn’t. It has helped a lot during this 
year to bear the COVID-19 pandemic. I will not go deeper into all the principles of Estocism in this post, but
in 2021 I want to delve into this way of thinking and apply it to different aspects of my personal and professional 
life.</p>

<p><strong>Typing</strong> (<em>high priority</em>). I think that typing fast and with precision can improve your productivity. Once you develop 
the muscle memory that allows you to type without thinking about it, you can focus more on what you type (i.e. code)
and less in what is the next letter you have to press on your keyboard. Some of the things I want to achieve:</p>
<ul>
  <li>Finish <a href="https://www.typing.com/">this</a> and <a href="https://www.typingclub.com/">this</a> online typing courses</li>
  <li>Improve my average typing speed, which is about 25 words per minute at the moment</li>
  <li>Improve my precision, which is about 80% for letters and about 50% for numbers if I don’t look at the keyboard</li>
</ul>

<p><strong>Trekking</strong> (<em>medium priority</em>):</p>
<ul>
  <li>Go at least twice a month to walk in the mountains near Madrid</li>
  <li>Go outside Madrid on a trip to walk some trails at least twice this year (if COVID restrictions allow it)</li>
</ul>

<p><strong>Raspberry</strong> (<em>medium priority</em>) After <a href="https://www.alfonsoalba.com/2020/07/12/the-raspberry-pi-project-hardware.html">buying a Raspberry Pi 4</a>
in 2020, 2021 will be the year in which I will put it to work for me:</p>
<ul>
  <li>Add a VPN to be able to access my Home network from the outside</li>
  <li>Create a self-hosted CD/CI with Jenkins</li>
  <li>Play the Gong when some events happen, like when we get a new user in our SaaS software for example.</li>
</ul>

<p><strong>Surgery</strong> (<em>high priority</em>). I tore the Supraspinatus tendon of my right shoulder about one year and a half ago. It’s 
not a critical wound at the moment meaning that I can still work and carry on with my life and do some exercise. However, 
this year I want to get the surgery to heal it. It can get worse as I age and there some activities that I enjoy doing 
like Body Attack or paddle that I can’t do at the moment. I hope I can resume them after the surgery.</p>

<p><a href="https://en.wikipedia.org/wiki/Sketchnoting"><strong>Sketchnoting</strong></a> (<em>medium priority</em>). <a href="https://twitter.com/oyabun">Javier Alonso</a>
from <a href="https://dibujandocharlas.com/">dibujandocharlas.com</a> presented a webminar this year about Sketchnoting. It was 
quite inspiring and I started to experiment with it. For this year I want to:</p>
<ul>
  <li>Give talks and use Sketchnoting techniques to make the slides</li>
  <li>Use it also for the slides that I use in my courses</li>
</ul>

<h2 id="profesional-goals">Profesional goals</h2>

<p><strong>Blog &amp; Newsletter</strong> (<em>high priority</em>)</p>
<ul>
  <li>Continue to post in this blog, at least twice a month</li>
  <li>Start the newsletter “A DevOps Diary” in which I share things related to my day to day job. Problems, solutions,
events, articles, talks, or StackOverflow questions that I find interesting or useful for my day to day work.</li>
  <li>After 6 years, start writing in the blog for <a href="https://www.cursodegit.com">cursodegit.com</a> again.</li>
</ul>

<p><strong>Training</strong> (<em>medium priority</em>)</p>
<ul>
  <li>Improve the course materials for <a href="https://www.cursodegit.com">cursodegit.com</a></li>
  <li>Give the advance git course to a company outside Spain</li>
  <li>Improve and work on the other courses: Docker, Jenkins, and Kubernetes</li>
  <li>Spin up my self-hosted academy and start giving the courses in it</li>
</ul>

<p><strong>Meetup</strong> (<em>high priority</em>) Start the meetups of the 
<a href="https://www.meetup.com/Spanish-Git-Meetup/">Spanish Git Meetup</a> again, at least once per month.</p>

<p><strong>Video Channels</strong> (<em>medium priority</em>) Create YouTube channels to complement the blog posts with videos. I have no 
equipment or experience recording videos so let’s see what comes out.</p>

<p><strong>Learning</strong> (<em>high priority</em>) Last but not least, I want to dig deeper into the following technologies:</p>
<ul>
  <li>Docker, Podman, and Kubernetes</li>
  <li>Bash</li>
  <li>Python</li>
  <li>Groovy / Java and Jenkins</li>
</ul>

<h2 id="lets-see">Let’s see…</h2>

<p>As I mentioned at the beginning of the post, this is the first year that I publicly post my objectives. I think they are
quite ambitious, I have high hopes for 2021. Let’s see how it goes. I can’t wait for the recap next year!</p>]]></content><author><name></name></author><category term="Other" /><summary type="html"><![CDATA[My personal and professional goals for 2021. Even though I set goals every Christmas, I usually forget about them around February. I'm sure that writing them down and sharing them here will help me to revisit them often and keep my focus on what I want to achieve.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://0.0.0.0:4000/assets/images/cards/objectives_for_2021.jpg" /><media:content medium="image" url="http://0.0.0.0:4000/assets/images/cards/objectives_for_2021.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">screen vs. nohup - Which one is better?</title><link href="http://0.0.0.0:4000/2020/12/28/screen-vs-nohup.html" rel="alternate" type="text/html" title="screen vs. nohup - Which one is better?" /><published>2020-12-28T00:00:00+01:00</published><updated>2020-12-28T00:00:00+01:00</updated><id>http://0.0.0.0:4000/2020/12/28/screen-vs-nohup</id><content type="html" xml:base="http://0.0.0.0:4000/2020/12/28/screen-vs-nohup.html"><![CDATA[<h2 id="tldr">TL;DR</h2>

<p>A follow-up of <a href="/2020/12/06/the-screen-command.html">the previous post about the screen command</a>. <code class="language-plaintext highlighter-rouge">nohup</code> uses
less memory than <code class="language-plaintext highlighter-rouge">screen</code>, that is the price to pay for all those nice features that come with <code class="language-plaintext highlighter-rouge">screen</code>.
Furthermore, <code class="language-plaintext highlighter-rouge">screen</code> must be installed as a separate package while <code class="language-plaintext highlighter-rouge">nohup</code> usually is distributed as
part of core utilities in almost any Linux distribution. However, if you need to run scripts that accept parameters then
<code class="language-plaintext highlighter-rouge">screen</code> will make your life easier.</p>

<p>They are different tools and difficult to compare. The best thing to do is understand both of them and use the right one
depending on your case.</p>

<h2 id="availability">Availability</h2>

<p><code class="language-plaintext highlighter-rouge">nohup</code> is a command that I’ve found in every Linux distribution that I’ve worked with. It was present also in Solaris
and BSD systems that I worked with a few years ago. You can be confident that when you open a terminal in any Linux 
box, the <code class="language-plaintext highlighter-rouge">nohup</code> command will be there. Let’s have a look at some major Linux distributions:</p>

<ul>
  <li><a href="https://packages.ubuntu.com/bionic/coreutils">Ubuntu</a> and <a href="https://packages.debian.org/buster/coreutils">debian</a> 
include the command in the <code class="language-plaintext highlighter-rouge">coreutils</code> package, which is a fundamental part of the operating system.</li>
  <li><a href="https://fedora.pkgs.org/33/fedora-aarch64/coreutils-8.32-12.fc33.aarch64.rpm.html">Fedora</a>, 
<a href="https://centos.pkgs.org/8/centos-baseos-aarch64/coreutils-8.30-8.el8.aarch64.rpm.html">Centos</a> and 
<a href="https://opensuse.pkgs.org/15.2/j.eng-x86_64/coreutils-8.29-lp152.8.3.i586.rpm.html">OpenSuse</a> also provide
the <code class="language-plaintext highlighter-rouge">nohup</code> command as part of their <code class="language-plaintext highlighter-rouge">coreutils</code> package.</li>
  <li>Even <a href="https://busybox.net/">busybox</a> which is a tool that provides a fairly complete environment for any small or 
embedded linux systems includes the <code class="language-plaintext highlighter-rouge">nohup</code> command by default.</li>
</ul>

<p><code class="language-plaintext highlighter-rouge">nohup</code> also comes installed in macOS, which it’s not surprising at all given that macOS is based on BSD.</p>

<p>On the contrary, <code class="language-plaintext highlighter-rouge">screen</code> must be installed as a separate package. This means that you might stumble upon machines in 
which it’s not installed. For example, I still have access to a shared hosting server that you could access using ssh. I had 
a very limited amount of linux commands available and <code class="language-plaintext highlighter-rouge">screen</code> was not one of them. But you know what? <code class="language-plaintext highlighter-rouge">nohup</code> is installed there!</p>

<p>For this reason, it’s a good idea to know how <code class="language-plaintext highlighter-rouge">nohup</code> works and what you can and can’t do with it. It will be like 
that good old friend that you know since kindergarten… it will always be there for you.</p>

<blockquote>
  <p><code class="language-plaintext highlighter-rouge">nohup</code> will always be there so my suggestion is that you should learn how to use it.</p>
</blockquote>

<h2 id="user-input">User input</h2>

<p><code class="language-plaintext highlighter-rouge">nohup</code> is good for running processes in the background when those processes don’t require user input. For 
example, let’s make a backup of our user folder using <code class="language-plaintext highlighter-rouge">nohup</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&gt; nohup tar cfj /tmp/mybackup.tar.bz2 /home/myuser &amp;
appending output to nohup.out
&gt;
</code></pre></div></div>

<blockquote>
  <p>Processes that don’t require user input are great to use with <code class="language-plaintext highlighter-rouge">nohup</code>.</p>
</blockquote>

<p>However, if the process requires user input, then <code class="language-plaintext highlighter-rouge">screen</code> it’s better for the task. As an example, 
we <a href="https://www.atlassian.com/migration/journey-to-cloud">still have a Jira Server intallation</a> in my current project.
We have a documented procedure to upgrade our instances when a new Jira version is released and that procedure
involves a script that asks you different questions during the upgrade process. We use <code class="language-plaintext highlighter-rouge">screen</code> for that.</p>

<p>Using screen we can start the Jira upgrade process, answer whatever questions the upgrade script asks and let it work for
some time in the background while it performs the backups and other stuff. We can even close the ssh connection to the 
server. Later, we can ssh back to the server, reattach to the screen and continue with the last part of the installation.
I can’t do that with <code class="language-plaintext highlighter-rouge">nohup</code>.</p>

<h2 id="terminal-window-manager">Terminal window manager</h2>

<p><code class="language-plaintext highlighter-rouge">screen</code> is good to manage multiple terminals. For example, you can open a terminal to do some backups and another 
terminal to configure your nginx server, all of this with one ssh connection. You can give them a meaningful name 
with the <code class="language-plaintext highlighter-rouge">-S</code> option and switch from one to another:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&gt; screen -s backups tar cfpj /tmp/backup.tar.bz2 /home/myuser
    (detach with CTRL+A d)
&gt; screen -s nginx_config 
    (detach with CTRL+A d)
&gt; screen -ls
There are screens on:
	16531.nginx_config	(Detached)
	16507.backups	(Detached)  
</code></pre></div></div>

<p>It’s more a terminal manager while <code class="language-plaintext highlighter-rouge">nohup</code> is a tool to run processes in the background detached from the terminal.</p>

<h2 id="conclusion">Conclusion</h2>

<p>Answering the question in the title of this post: there is no tool better than the other one. 
<code class="language-plaintext highlighter-rouge">nohup</code> and <code class="language-plaintext highlighter-rouge">screen</code> are different things and therefore difficult to compare. Being a terminal manager, <code class="language-plaintext highlighter-rouge">screen</code> is more powerful
and it’s a great add-on to your toolset if you work regularly with the command line. However, the availability of <code class="language-plaintext highlighter-rouge">nohup</code>
makes it almost a universal tool, so it’s good to know what you can and can’t do with it just in case you stumble upon
a system in which no terminal manager like <code class="language-plaintext highlighter-rouge">screen</code> can be installed.</p>]]></content><author><name></name></author><category term="Linux" /><category term="Commands" /><summary type="html"><![CDATA[A follow-up of the previous article about the screen command. Given the ton of features that screen has, when is it better to use nohup?]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://0.0.0.0:4000/assets/images/cards/two_knights_face_to_face.jpg" /><media:content medium="image" url="http://0.0.0.0:4000/assets/images/cards/two_knights_face_to_face.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">The screen command</title><link href="http://0.0.0.0:4000/2020/12/06/the-screen-command.html" rel="alternate" type="text/html" title="The screen command" /><published>2020-12-06T14:00:00+01:00</published><updated>2020-12-06T14:00:00+01:00</updated><id>http://0.0.0.0:4000/2020/12/06/the-screen-command</id><content type="html" xml:base="http://0.0.0.0:4000/2020/12/06/the-screen-command.html"><![CDATA[<h2 id="tldr">TL;DR</h2>

<p>I discovered the <code class="language-plaintext highlighter-rouge">screen</code> command recently and it was love at first sight. Before 
knowing about <code class="language-plaintext highlighter-rouge">screen</code>, I was using <code class="language-plaintext highlighter-rouge">nohup</code> to leave commands running in the background 
after closing my SSH connection.  The rich set of options that the <code class="language-plaintext highlighter-rouge">screen</code> command has, 
made <code class="language-plaintext highlighter-rouge">nohup</code> <em>obsolete</em> for me. In this post I show you my most common use case of <code class="language-plaintext highlighter-rouge">screen</code>.</p>

<h2 id="what-is-screen">What is <code class="language-plaintext highlighter-rouge">screen</code></h2>

<p><code class="language-plaintext highlighter-rouge">screen</code> is a <a href="https://en.wikipedia.org/wiki/Terminal_multiplexer">terminal multiplexer</a>. Quoting the 
<a href="https://man7.org/linux/man-pages/man1/screen.1.html">man page</a>:</p>

<blockquote>
  <p>Screen is a full-screen window manager that multiplexes
a physical terminal between several processes (typically 
interactive shells). Each virtual terminal provides the 
functions of a DEC VT100 terminal and, in addition, 
several control functions from the ISO 6429 (ECMA 48, 
ANSI X3.64) and ISO 2022 standards (e.g. insert/delete 
line and support for multiple character sets). There is 
a scrollback history buffer for each virtual terminal 
and a copy-and-paste mechanism that allows moving text 
regions between windows.</p>
</blockquote>

<p>The <a href="https://man7.org/linux/man-pages/man1/screen.1.html">man page</a> is more than 4000 lines long. And that’s
because <code class="language-plaintext highlighter-rouge">screen</code> is packed with options.</p>

<p>You can do quite a few things with <code class="language-plaintext highlighter-rouge">screen</code>:</p>

<ul>
  <li>Open an ssh connection, run a job in the background inside a <em>screen</em> and close the connection. 
You reconnect back later through ssh and open the <em>screen</em> you left open to see how your command
is doing.</li>
  <li>Open a screen with a shell in it and share it with other people connected to the 
same machine through ssh.</li>
  <li>Use it to run several processes in the background using the same SSH connection. I see it as a
replacement of the good old <code class="language-plaintext highlighter-rouge">jobs</code>, <code class="language-plaintext highlighter-rouge">fg</code>, and <code class="language-plaintext highlighter-rouge">bg</code> bash builtins.</li>
</ul>

<p>In this post, I will describe the first use case, which is the most common for me.</p>

<h2 id="installation">Installation</h2>

<p>In Linux or Windows 10 with WSL, you can use the package manager of your distribution to install it. 
In Debian based distributions you can use apt:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> <span class="nb">sudo </span>apt <span class="nb">install </span>screen
</code></pre></div></div>

<p>In macOS, the best way to install it is by using homebrew:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> brew <span class="nb">install </span>screen
</code></pre></div></div>

<h2 id="using-screen-to-run-long-running-commands">Using <code class="language-plaintext highlighter-rouge">screen</code> to run long-running commands</h2>

<p>In my current company (Platform161) we run scripts that take several hours to finish. Although
we normally use Jenkins to run them, from time to time we need to execute them manually
connecting to the machine using ssh.</p>

<p>For those that are not used to work with Linux, if you run a script just by connecting to the machine
using ssh and executing it, the script will be run inside your session. If you log out or your internet 
connection fails, your session will be terminated and your job as well. It’s not enough to run
the script in the background since it will be still running in your session.</p>

<p>If you have a backup script that lasts four hours, you don’t want to have it running inside
attached to your session. Losing your internet connection 10 seconds before your 3-hout-long job finishes 
and having to run it again is something extremely infuriating.</p>

<p>We will run the command using <code class="language-plaintext highlighter-rouge">screen</code> instead. Let’s connect through ssh to our machine:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> ssh alfonso@mymachine.com
</code></pre></div></div>

<p>Once we logged in, we run the <code class="language-plaintext highlighter-rouge">screen</code> command:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>alfonso@mymachine.com <span class="o">&gt;</span> screen
</code></pre></div></div>

<p>and we will be welcomed with a message:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Screen version 4.00.03 <span class="o">(</span>FAU<span class="o">)</span> 23-Oct-06

Copyright <span class="o">(</span>c<span class="o">)</span> 1993-2002 Juergen Weigert, Michael Schroeder
Copyright <span class="o">(</span>c<span class="o">)</span> 1987 Oliver Laumann

This program is free software<span class="p">;</span> you can redistribute it and/or modify it under 
the terms of the GNU General Public License as published by the Free Software 
Foundation<span class="p">;</span> either version 2, or <span class="o">(</span>at your option<span class="o">)</span> any later version.

This program is distributed <span class="k">in </span>the hope that it will be useful, but WITHOUT ANY 
WARRANTY<span class="p">;</span> without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 
PARTICULAR PURPOSE. See the GNU General Public License <span class="k">for </span>more details.

You should have received a copy of the GNU General Public License along with 
this program <span class="o">(</span>see the file COPYING<span class="o">)</span><span class="p">;</span> <span class="k">if </span>not, write to the Free Software Foundation, 
Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

Send bugreports, fixes, enhancements, t-shirts, money, beer &amp; pizza to screen@uni-erlangen.de

<span class="o">[</span>Press Space or Return to end.]
</code></pre></div></div>

<p>After pressing Space or return, <code class="language-plaintext highlighter-rouge">screen</code> will run a shell. Inside that shell, we execute
our long-running command:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>alfonso@mymachine.com <span class="o">&gt;</span> ./long_running_script.sh
The script starts to run...
progress: 1%...2%...
</code></pre></div></div>

<p>Let’s say the script takes about 3 hours to complete. Since we are running it inside a 
<code class="language-plaintext highlighter-rouge">screen</code> session, we can now detach from the screen by pressing <code class="language-plaintext highlighter-rouge">CTRL+A</code> and then <code class="language-plaintext highlighter-rouge">d</code>. 
After doing that, we go back to the shell we opened when we logged in using ssh:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>alfonso@mymachine.com <span class="o">&gt;</span> screen
<span class="o">[</span>detached from 2661.pts-0.mycomputer]
alfonso@mymachine.com <span class="o">&gt;</span> _
</code></pre></div></div>

<p>Now, our script is not attached to our session but to the <em>screen</em> we created. This way, We don’t depend
on the internet connection for our script to run!</p>

<p>…and we can just logout and come back later to check the progress. Before logging out 
from <code class="language-plaintext highlighter-rouge">mymachine.com</code> let’s have a look at the list of screens that are open:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>alfonso@mymachine.com <span class="o">&gt;</span> screen <span class="nt">-ls</span>

There is a screen on:
	2661.pts-0.mycomputer	<span class="o">(</span>12/07/20 10:54:54<span class="o">)</span>	<span class="o">(</span>Detached<span class="o">)</span>
1 Socket <span class="k">in</span> /run/screen/S-alfonso.

alfonso@mymachine.com <span class="o">&gt;</span> _
</code></pre></div></div>

<p>As you can imagine, we can re-attach later to that screen using the identifier <code class="language-plaintext highlighter-rouge">2661.pts-0.mycomputer</code>.
Since this is a command that takes a long time, we will close the ssh connection and check again later.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>alfonso@mymachine.com <span class="o">&gt;</span> <span class="nb">exit</span>
<span class="o">&gt;</span> _
</code></pre></div></div>

<h2 id="re-attaching-to-the-screen">Re-attaching to the screen</h2>

<p>After a couple of hours, we want to check the progress of our long-running script. To do that,
let’s connect again to our machine using ssh:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> ssh alfonso@mymachine.com
</code></pre></div></div>

<p>Let’s see if our long-running script is stil there. To do that, we get a list of the available screens:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>alfonso@mymachine.com <span class="o">&gt;</span> screen <span class="nt">-ls</span>

There is a screen on:
	2661.pts-0.mycomputer	<span class="o">(</span>12/07/20 10:54:54<span class="o">)</span>	<span class="o">(</span>Detached<span class="o">)</span>
1 Socket <span class="k">in</span> /run/screen/S-alfonso.

alfonso@mymachine.com <span class="o">&gt;</span> _
</code></pre></div></div>

<p>Yes,it’s still there. We re-attach to the screen <code class="language-plaintext highlighter-rouge">2661.pts-0.mycomputer</code> to see how are things going:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>alfonso@mymachine.com <span class="o">&gt;</span> screen <span class="nt">-r</span> 2661.pts-0.mycomputer
</code></pre></div></div>

<p>after running this command, the screen will open and we can check the progress</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>alfonso@mymachine.com <span class="o">&gt;</span> ./long_running_script.sh
The script starts to run...
progress: ...10%...20%...30%...40%...50%...60%..
</code></pre></div></div>

<p>We can log out and check again later.</p>

<h2 id="saving-output-to-a-file">Saving output to a file</h2>

<p>We can continue to connect through ssh and check the progress of the script. Eventually, the script
will finish and the <code class="language-plaintext highlighter-rouge">screen -ls</code> command will return that no screens have been found:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>alfonso@mymachine.com <span class="o">&gt;</span> screen <span class="nt">-ls</span>
No Sockets found <span class="k">in</span> /tmp/uscreens/S-alfonso
</code></pre></div></div>

<p>After the script finishes running, the screen is closed and the output
of our command will be lost. Unfortunately, we don’t know if the command was 
successful or not. If I want to save the output of the screen 
and keep it after the script ends, we should
use the <code class="language-plaintext highlighter-rouge">-L</code> option. We can also use the <code class="language-plaintext highlighter-rouge">-Logfile</code> option to select where to store the output:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>alfonso@mymachine.com <span class="o">&gt;</span> screen <span class="nt">-L</span> <span class="nt">-Logfile</span> long_running_script_ouput.txt
</code></pre></div></div>

<p>With these options, once the script finishes, we can look
at the output of the file <code class="language-plaintext highlighter-rouge">long_running_script_ouput.txt</code> to see what happened.</p>

<h2 id="credits">Credits</h2>

<ul>
  <li>My colleague Manuel Molina for introducing me to this command that I didn’t know about. Much
more convenient than <code class="language-plaintext highlighter-rouge">nohup</code>.</li>
</ul>]]></content><author><name></name></author><category term="Linux" /><category term="Commands" /><summary type="html"><![CDATA[The screen command has many use cases. In this post I show you how I use it to run long-running scripts in the background so I can disconnect from the server while the script runs. A very useful command to have in your toolset!]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://0.0.0.0:4000/assets/images/cards/multiple_screens.jpg" /><media:content medium="image" url="http://0.0.0.0:4000/assets/images/cards/multiple_screens.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Running exercism inside a docker container</title><link href="http://0.0.0.0:4000/2020/11/28/exercism-using-docker.html" rel="alternate" type="text/html" title="Running exercism inside a docker container" /><published>2020-11-28T02:00:00+01:00</published><updated>2020-11-28T02:00:00+01:00</updated><id>http://0.0.0.0:4000/2020/11/28/exercism-using-docker</id><content type="html" xml:base="http://0.0.0.0:4000/2020/11/28/exercism-using-docker.html"><![CDATA[<h2 id="tldr">TL;DR</h2>

<p><a href="https://www.exercism.io">exercism.io</a> is a nice place to learn programming languages. It has a command-line
tool to download and upload the exercises. In this post, I configure a docker image and run 
<code class="language-plaintext highlighter-rouge">exercism</code> inside a container.</p>

<h2 id="docker-concepts-involved">Docker concepts involved</h2>

<p>Running <code class="language-plaintext highlighter-rouge">exercism</code> inside docker gives us a great opportunity to learn and practice a few docker concepts. 
I’ve already talked about why I’m using docker as much as possible, even when it’s
overkill (like in this example). If you want to know about it, you can 
<a href="/2020/10/13/about-this-blog-docker.html">read about it in this post</a>.</p>

<p>If you follow the example, you will need to dig deeper into the following docker concepts:</p>

<ul>
  <li>Build custom docker images using a <code class="language-plaintext highlighter-rouge">Dockerfile</code></li>
  <li>Build arguments</li>
  <li>Differences between images and containers</li>
  <li>Run containers</li>
  <li>Bind volumes</li>
  <li>Bind volumes and file ownership/permissions</li>
</ul>

<p>Explaining these docker concepts is out of the scope of this post, so I will not go into the details of any of them.</p>

<h2 id="before-you-start">Before you start..</h2>

<p>You should be able to run all the commands that I use in this article in the following systems:</p>
<ul>
  <li>Windows 10 + WSL2 (I used the Ubuntu 20.04LTS image) and Docker for Windows installed</li>
  <li>Any Linux distribution with Docker installed</li>
  <li>Mac OS and Docker for Mac installed</li>
</ul>

<p>You will also need an account in the <code class="language-plaintext highlighter-rouge">exercism.io</code> website to be able to download an exercise.</p>

<h2 id="about-exercismio">About <code class="language-plaintext highlighter-rouge">exercism.io</code></h2>

<p>Before we start, what is <code class="language-plaintext highlighter-rouge">exercism.io</code>? Developed initially by 
the one and only <a href="http://www.kytrinyx.com">Katrina Owen</a>, it’s a 
community-driven site with the goal of helping people improve their coding skills.</p>

<blockquote>
  <p>Code practice and mentorship for everyone</p>

  <p>Level up your programming skills with 3,442 exercises across 52 languages, 
and insightful discussion with our dedicated team of welcoming mentors.</p>

  <p>Exercism is 100% free forever.</p>
</blockquote>

<p>I was already registered at a couple of similar websites like 
<a href="https://www.codewars.com/users/aalbagarcia">CodeWars</a>
or the <a href="https://projecteuler.net">euler project</a> when I discovered <code class="language-plaintext highlighter-rouge">excercism.io</code>. But I fell in love with it very fast:</p>

<ul>
  <li>I can use my computer to solve the problems so I can work offline and use whatever version I want to use 
of the tools.</li>
  <li>Since I work in my local computer, 
<a href="https://github.com/aag-learn/exercism">I can track my progress using git</a>. 
In CodeWars, for example, you have to use their interface whether you like it or not</li>
  <li>They have mentors that look at your code and give you meaningful feedback</li>
  <li>The challenges are well thought and designed with different degrees of difficulty</li>
  <li>The paths in which they organize the exercises make it very easy to track your progress and resume your 
work whenever you want to</li>
</ul>

<p>Don’t get me wrong. I like CodeWars and the incredible community of people working on their challenges, but
the simplicity, clarity, and mentorship of <code class="language-plaintext highlighter-rouge">exercism.io</code> got me real fast. If you have a chance, give it a try.</p>

<h2 id="what-i-want-to-achieve">What I want to achieve</h2>

<ul>
  <li>Be able to run the <code class="language-plaintext highlighter-rouge">exercism</code> executable in my computer inside a docker container</li>
  <li>Be able to do the exercises of the <a href="https://exercism.io/tracks/bash">bash track</a> 
without installing bash.</li>
</ul>

<p>Yes, I know that any MacOS or Linux box have bash installed, but what about windows? 💁‍♂️ In this 
post I will focus on the first step.</p>

<h2 id="the-dockerfile">The <code class="language-plaintext highlighter-rouge">Dockerfile</code></h2>

<p>The exercism.io webpage <a href="https://exercism.io/cli-walkthrough">has a nice walkthrough</a> that you can follow to install the client in Windows, Linux, and Mac OS. We will use the instructions for the Linux walkthrough
to create our <code class="language-plaintext highlighter-rouge">Dockerfile</code>:</p>

<div class="language-docker highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="k">FROM</span><span class="s"> debian:stable</span>

<span class="k">ARG</span><span class="s"> USERID</span>
<span class="k">RUN </span>adduser <span class="nt">--uid</span> <span class="k">${</span><span class="nv">USERID</span><span class="k">:-</span><span class="nv">1000</span><span class="k">}</span> <span class="nt">--gecos</span> <span class="s2">""</span> <span class="nt">--disabled-password</span> exercism <span class="o">&amp;&amp;</span> <span class="se">\
</span>    apt-get update <span class="o">&amp;&amp;</span> <span class="se">\
</span>    apt-get <span class="nb">install</span> <span class="nt">-y</span> curl bats vim <span class="o">&amp;&amp;</span> <span class="se">\
</span>    curl <span class="nt">-L</span> https://github.com/exercism/cli/releases/download/v3.0.13/exercism-3.0.13-linux-x86_64.tar.gz <span class="nt">--output</span> exercism-3.0.13-linux-x86_64.tar.gz <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">mkdir </span>exercism-3.0.13-linux-x86_64 <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">tar </span>xfz exercism-3.0.13-linux-x86_64.tar.gz <span class="nt">-C</span> ./exercism-3.0.13-linux-x86_64 <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">cp </span>exercism-3.0.13-linux-x86_64/exercism /usr/local/bin/ <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">mkdir</span> /app <span class="o">&amp;&amp;</span> <span class="se">\
</span>    <span class="nb">chown </span>exercism /app <span class="o">&amp;&amp;</span> <span class="se">\
</span>    apt-get clean

<span class="k">WORKDIR</span><span class="s"> /app</span>
<span class="k">USER</span><span class="s"> exercism</span>

<span class="k">ENTRYPOINT</span><span class="s"> ["/usr/local/bin/exercism"]</span>

</code></pre></div></div>

<p>Let’s go through the <code class="language-plaintext highlighter-rouge">Dockerfile</code> and comment on it.</p>

<div class="language-docker highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="s"> debian:stable</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">debian:stable</code> is our base image. It has everything we need to run the <code class="language-plaintext highlighter-rouge">exercism</code> executable and an up-to-date bash version (5.0.3).</p>

<div class="language-docker highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">ARG</span><span class="s"> USERID</span>
<span class="k">RUN </span>adduser <span class="nt">--uid</span> <span class="k">${</span><span class="nv">USERID</span><span class="k">:-</span><span class="nv">1000</span><span class="k">}</span> <span class="nt">--gecos</span> <span class="s2">""</span> <span class="nt">--disabled-password</span> exercism
</code></pre></div></div>
<p><a href="https://security.stackexchange.com/questions/176206/docker-runs-container-processes-as-root-should-i-be-worried">To avoid running processes as root inside our container</a>, 
we create an <code class="language-plaintext highlighter-rouge">exercism</code> user that we will use to run processes inside the containers created from this 
image.</p>

<p>If you look at the <code class="language-plaintext highlighter-rouge">adduser</code> command above, you will notice that the <code class="language-plaintext highlighter-rouge">uid</code> of the user inside the container
is passed as a build argument. Later, we will pass the <code class="language-plaintext highlighter-rouge">uid</code> of the user in the host
computer so the user in the host that builds and runs the image will have the same <code class="language-plaintext highlighter-rouge">uid</code> as the user 
<code class="language-plaintext highlighter-rouge">exercism</code> inside the container.</p>

<p>We do this because later we will use
bind-volumes to share files between the host and the container. Since both users (the host user and the
<code class="language-plaintext highlighter-rouge">exercism</code> user) have the same <code class="language-plaintext highlighter-rouge">uid</code>, any files created inside the container will belong to the user in the host.</p>

<div class="language-docker highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">RUN </span>...
    apt-get install -y curl &amp;&amp; \
</code></pre></div></div>
<p>Install <code class="language-plaintext highlighter-rouge">curl</code> (we’ll use it later to download the <code class="language-plaintext highlighter-rouge">exercism</code> package from github.com)</p>

<div class="language-docker highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">RUN </span>...
    curl -L https://github.com/exercism/cli/releases/download/v3.0.13/exercism-3.0.13-linux-x86_64.tar.gz --output exercism-3.0.13-linux-x86_64.tar.gz &amp;&amp; \
    mkdir exercism-3.0.13-linux-x86_64 &amp;&amp; \
    tar xfz exercism-3.0.13-linux-x86_64.tar.gz -C ./exercism-3.0.13-linux-x86_64 &amp;&amp; \
    cp exercism-3.0.13-linux-x86_64/exercism /usr/local/bin/ &amp;&amp; \
</code></pre></div></div>
<p>Download, extract, and copy the latest version of the exercism client into <code class="language-plaintext highlighter-rouge">/usr/local/bin</code>.</p>

<div class="language-docker highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">RUN </span>...
    apt-get clean &amp;&amp; \
    rm -rf /var/lib/apt/lists/* &amp;&amp; \
</code></pre></div></div>
<p>Clear the <code class="language-plaintext highlighter-rouge">apt</code> cache to make our image smaller (more info <a href="https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#run">here</a>).</p>

<div class="language-docker highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">RUN </span>...
    mkdir /app &amp;&amp; \
    chown exercism /app &amp;&amp; \
<span class="k">WORKDIR</span><span class="s"> /app</span>
<span class="k">USER</span><span class="s"> exercism</span>
</code></pre></div></div>
<p>Create the path that we’ll use to mount the bind-volume later.</p>

<div class="language-docker highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">ENTRYPOINT</span><span class="s"> ["/usr/local/bin/exercism"]</span>
</code></pre></div></div>
<p>Prepare the container to be <a href="https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#entrypoint">run as an executable</a>.</p>

<h2 id="the-makefile">The Makefile</h2>

<p>Once the <code class="language-plaintext highlighter-rouge">Dockerfile</code> is ready, we’ll use the <code class="language-plaintext highlighter-rouge">make</code> utility to help us run the docker commands. Using 
your favorite editor, create a new file called <code class="language-plaintext highlighter-rouge">Makefile</code> with the following content:</p>

<div class="language-make highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">USERID</span> <span class="o">:=</span> <span class="p">$(</span>shell <span class="nb">id</span> <span class="nt">-u</span><span class="p">)</span>

<span class="nl">build</span><span class="o">:</span>
      <span class="nv">DOCKER_BUILDKIT</span><span class="o">=</span>1 docker build <span class="nt">-t</span> exercism:latest <span class="nt">--build-arg</span> <span class="nv">USERID</span><span class="o">=</span><span class="p">${</span>USERID<span class="p">}</span> .
</code></pre></div></div>

<p>As I mentioned in section <em>The Dockerfile</em> above, we 
are building the image with a user (<code class="language-plaintext highlighter-rouge">exercism</code>) that will have the same user id as our user in the host
system. The first line of the <code class="language-plaintext highlighter-rouge">Makefile</code> creates a variable <code class="language-plaintext highlighter-rouge">USERID</code> to store the user id using the built-in <code class="language-plaintext highlighter-rouge">shell</code> command of the Make utility.</p>

<p>In the definition of the <code class="language-plaintext highlighter-rouge">build</code> target, we pass the user argument <code class="language-plaintext highlighter-rouge">USERID</code> to the build command using 
the <code class="language-plaintext highlighter-rouge">--build-arg</code> option.</p>

<p>Save the file. We will use it to build the image in the next step.</p>

<h2 id="build-the-image">Build the image</h2>

<p>From the console run the following command:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> make build
</code></pre></div></div>

<p>The build will start and after a few seconds you should see an output similar to this one:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>DOCKER_BUILDKIT=1 docker build -t exercism:latest --build-arg USERID=501 .
[+] Building 31.9s (7/7) FINISHED
 =&gt; [internal] load .dockerignore
 =&gt; =&gt; transferring context: 2B
 =&gt; [internal] load build definition from Dockerfile
 =&gt; =&gt; transferring dockerfile: 746B
 =&gt; [internal] load metadata for docker.io/library/debian:stable
 =&gt; CACHED [1/3] FROM docker.io/library/debian:stable@sha256:3ac5e3f2fdd73d124e538d0a21f9fa9ba273bebed18af5f38bd87c2e69c04cb5
 =&gt; [2/3] RUN adduser --uid 501 --gecos "" --disabled-password exercism &amp;&amp;     apt-get update &amp;&amp;     apt-get install -y curl ...
 =&gt; [3/3] WORKDIR /app
 =&gt; exporting to image
 =&gt; =&gt; exporting layers
 =&gt; =&gt; writing image sha256:c91010d93b29729c2315753f7dec65d4747bdff13d5e611907b7867d2671180a
 =&gt; =&gt; naming to docker.io/library/exercism:latest
</code></pre></div></div>

<p>The last two lines are the ones we are looking for: they mean that the image was built and ready to use.</p>

<h2 id="running-the-command-exercism-troubleshoot">Running the command <code class="language-plaintext highlighter-rouge">exercism troubleshoot</code></h2>

<p>Let’s run the <code class="language-plaintext highlighter-rouge">exercism troubleshoot</code> command:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> docker run <span class="nt">--rm</span> exercism troubleshoot

Troubleshooting Information
<span class="o">===========================</span>

Version
<span class="nt">----------------</span>
Current: 3.0.13
Latest:  3.0.13


Operating System
<span class="nt">----------------</span>
OS:           linux
Architecture: amd64


Configuration
<span class="nt">----------------</span>
Home:      /home/exercism
Workspace: /home/exercism/exercism <span class="o">(</span>default<span class="o">)</span>
Config:    /home/exercism/.config/exercism
API key:   &lt;not configured&gt;
Find your API key at https://exercism.io/my/settings

API Reachability
<span class="nt">----------------</span>

GitHub:
    <span class="k">*</span> https://api.github.com
    <span class="k">*</span> <span class="o">[</span>connected]
    <span class="k">*</span> 43.0216ms

Exercism:
    <span class="k">*</span> https://api.exercism.io/v1/ping
    <span class="k">*</span> <span class="o">[</span>connected]
    <span class="k">*</span> 485.9616ms


If you are having trouble please file a GitHub issue at
https://github.com/exercism/exercism.io/issues and include
this information.
</code></pre></div></div>

<p>If you see this output, your image is ready!</p>

<h2 id="configure-exercism">Configure <code class="language-plaintext highlighter-rouge">exercism</code>`</h2>

<p>Looking at the output of the previous command, we can get a hint of the bind volumes that we need to be
able to run the command:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Configuration
----------------
Home:      /home/exercism
Workspace: /home/exercism/exercism (default)
Config:    /home/exercism/.config/exercism
API key:   &lt;not configured&gt;
Find your API key at https://exercism.io/my/settings
</code></pre></div></div>

<p>We will need two bind volumes. One for <code class="language-plaintext highlighter-rouge">/home/exercism/exercism</code> and another one for <code class="language-plaintext highlighter-rouge">/home/exercism/.config/exercism</code>:</p>
<ul>
  <li>The directory <code class="language-plaintext highlighter-rouge">/home/exercism/exercism</code> inside the container will be bound to the directory of the host in which we are running the command, i.e <code class="language-plaintext highlighter-rouge">$(pwd)</code>.</li>
  <li>The directory <code class="language-plaintext highlighter-rouge">/home/exercism/.config/exercism</code> inside the container, will be bound to the local
directory of the host <code class="language-plaintext highlighter-rouge">$HOME/.config/exercism</code> so we can
save and reuse the configuration between different executions of the container.</li>
</ul>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> docker run <span class="se">\</span>
      <span class="nt">--rm</span> <span class="se">\</span>
      <span class="nt">-v</span> <span class="si">$(</span><span class="nb">pwd</span><span class="si">)</span>:/home/exercism/exercism <span class="se">\</span>
      <span class="nt">-v</span> <span class="nv">$HOME</span>/.config/exercism:/home/exercism/.config/exercism <span class="se">\</span>
      exercism:latest 
      configure <span class="nt">--token</span><span class="o">=</span>11___XXXXXX___ab <span class="nt">--workspace</span><span class="o">=</span>/home/exercism/exercism

You have configured the Exercism command-line client:

Config <span class="nb">dir</span>:                       /home/exercism/.config/exercism
Token:         <span class="o">(</span><span class="nt">-t</span>, <span class="nt">--token</span><span class="o">)</span>      11___XXXXXX___ab
Workspace:     <span class="o">(</span><span class="nt">-w</span>, <span class="nt">--workspace</span><span class="o">)</span>  /home/exercism/exercism
API Base URL:  <span class="o">(</span><span class="nt">-a</span>, <span class="nt">--api</span><span class="o">)</span>        https://api.exercism.io/v1      
</code></pre></div></div>

<p>That looks good!! Now, we should have a new file in our host computer holding the configuration of <code class="language-plaintext highlighter-rouge">exercism</code>:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> <span class="nb">cat</span> <span class="nv">$HOME</span>/.config/exercism/user.json
<span class="o">{</span>
  <span class="s2">"apibaseurl"</span>: <span class="s2">"https://api.exercism.io/v1"</span>,
  <span class="s2">"token"</span>: <span class="s2">"11___XXXXXX___ab"</span>,
  <span class="s2">"workspace"</span>: <span class="s2">"/home/exercism/exercism"</span>
<span class="o">}</span>
</code></pre></div></div>

<h2 id="create-an-alias">Create an alias</h2>

<p>We will create an alias to save us some typing. We add the following alias to the <code class="language-plaintext highlighter-rouge">.bashrc</code>, 
<code class="language-plaintext highlighter-rouge">.bash_profile</code> or <code class="language-plaintext highlighter-rouge">.zshrc</code> (depending on your Linux distribution or Operating System):</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">alias </span><span class="nv">exercism</span><span class="o">=</span><span class="s1">'docker run --rm -v $(pwd):/home/exercism/exercism -v $HOME/.config/exercism:/home/exercism/.config/exercism exercism:latest'</span>
</code></pre></div></div>

<p>This alias will be there the next time you open a new terminal. To make it available in the current shell
you can just run the command above to create the alias.</p>

<h2 id="get-our-first-exercise">Get our first exercise</h2>

<p>We are almost there. Let’s start our <a href="https://exercism.io/tracks/groovy">Groovy path</a> in exercism.io in <a href="https://exercism.io/mentored-mode-vs-independent-mode">mentored mode</a>.</p>

<p>To download the first exercise:</p>

<ol>
  <li>Create a folder <code class="language-plaintext highlighter-rouge">$HOME/learning/exercism</code> in which I will do all the exercises
    <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  ~&gt; <span class="nb">mkdir</span> <span class="nt">-p</span> learning/exercism
  ~&gt; <span class="nb">cd</span> ~/learning/exercism 
</code></pre></div>    </div>
  </li>
  <li>Using the alias that we created in the previous section, we download the exercise (the command I’m using 
  is copied from its page in excersim.io)
    <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  ~/learning/exercism <span class="o">&gt;</span> exercism download <span class="nt">--exercise</span><span class="o">=</span>hello-world <span class="nt">--track</span><span class="o">=</span>groovy
</code></pre></div>    </div>
  </li>
</ol>

<p>After solving the problem, which is out fo the scope of this article, you can submit the solution by 
running:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>~/learning/exercism <span class="o">&gt;</span> exercism submit groovy/hello-world/src/main/groovy/HelloWorld.groovy
</code></pre></div></div>

<h2 id="alternatives">Alternatives</h2>

<p>Instead of bind mount <code class="language-plaintext highlighter-rouge">/home/exercism/.config/exercism</code>, we can store the configuration inside the
image. This would be the subject of another post since it requires a different Dockerfile and a way to
handle the token appropriately.</p>

<h2 id="references-and-attributions">References and attributions</h2>

<ul>
  <li>The container image used for the article is a <a href="https://www.freepik.com/photos/background">Background photo created by evening_tao - www.freepik.com</a> (link to the photo <a href="https://www.freepik.com/free-photo/harbor-freight-blue-toned-images_1175715.htm">here</a>)</li>
</ul>]]></content><author><name></name></author><category term="Docker" /><category term="Learning" /><summary type="html"><![CDATA[exercism.io is a nice project to learn programming languages. It has a command-line utility called exercism and in this post I will configure a docker container to run it.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://0.0.0.0:4000/assets/images/cards/exercism_inside_a_docker_container_card.jpg" /><media:content medium="image" url="http://0.0.0.0:4000/assets/images/cards/exercism_inside_a_docker_container_card.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">About this blog: Working with multiple operating systems</title><link href="http://0.0.0.0:4000/2020/11/19/about-this-blog-working-with-multiple-operating-systems.html" rel="alternate" type="text/html" title="About this blog: Working with multiple operating systems" /><published>2020-11-19T23:00:00+01:00</published><updated>2020-11-19T23:00:00+01:00</updated><id>http://0.0.0.0:4000/2020/11/19/about-this-blog-working-with-multiple-operating-systems</id><content type="html" xml:base="http://0.0.0.0:4000/2020/11/19/about-this-blog-working-with-multiple-operating-systems.html"><![CDATA[<h2 id="tldr">TL;DR;</h2>

<p>The last time I used the Windows Operating System to do anything different than playing games was at the beginning of this century, around 2003. Since then, I’ve only used
Unix-like operating systems (like Linux and macOS) to do my job. 
This year I’ve decided that I want to be able to do my job using macOS, Linux and 
Windows 10 and in this post I’ll share with you why I’ve made this decision.</p>

<h2 id="it-all-started-with-a-blog-post">It all started with a blog post…</h2>

<p>Last year, in August, I found a link in <a href="https://rubyweekly.com/issues/461">RubyWeekly issue #461</a> to an article from Scott Hanselman titled
<a href="https://www.hanselman.com/blog/ruby-on-rails-on-windows-is-not-just-possible-its-fabulous-using-wsl2-and-vs-code"><em>Ruby on Rails on Windows is not just possible, it’s fabulous using WSL2 and VS Code</em></a>.</p>

<p>I read the article and it really got my attention. I had never seen Windows as an option to do my job until I read it.
It made me remember why I discarded Windows a long time ago: it was because it 
sucked so much compared to Unix. There was no package manager, the terminal (<code class="language-plaintext highlighter-rouge">cmd</code>)
was horrible, installing rails and developing rails apps was difficult, and compiling 
certain gems and their dependencies could be really frustrating. 
But that was almost 20 years ago. 
So I started to do some research to see what this Windows 10 OS had that made it so 
appealing to Scott Hanselman.</p>

<p>I installed a windows 10 virtual machine and tried the desktop, all the things that I hated
from Windows 8 were gone (specially the desktop) and I had to admit that it was good. I liked it. 
I started to dig deeper and I learned about
WLS, WSL2, and how Microsoft has been working to make things work better and faster.
And I think that they have done a great job integrating with Linux.</p>

<p>During this research, I read a lot of articles about people switching from Mac to Windows,
things they liked and they didn’t, applications they missed and what alternatives they used. 
YouTube and the internet are full of posts and videos about this topic.</p>

<p>I started to find more and more people saying that developing in Windows now was 
something very close to do it in macOS or Linux thanks to WSL. That they were 
able to install packages using APT, that the system was performant, and the experience
was great. I started to follow how WSL was evolving and I got excited with
<a href="https://docs.docker.com/docker-for-windows/wsl/">all the performance improvements that WSL2 brought to Docker for Windows</a>.</p>

<p>By the end of last year, I started to look for computers since I needed to replace mine. What I read 
about Windows 10 had so much impact on me that I even looked at the prices and specs of Surface 2 and Dell computers. 
I was seriously thinking about replacing my 5 years old MacBook Pro with a Windows box. 
Unfortunately for Microsoft and Dell, Apple released
the new 16-inch MacBook Pro in the last quarter I finally decided to buy it instead.</p>

<p>Even though I bought a MacBook Pro again, I committed myself to be able to do my job in any of the three 
major operating systems: macOS, Windows 10, and Linux. Why?</p>

<ul>
  <li>If I start a new job or a new project in which you are requested to use
a different operating system to the one I’m used to, I would be productive in a shorter
time. I would already know the tools and I would have been working on them for some time so
I would be ready to Rock ‘n’ Roll from day one.</li>
  <li>It will get me out of my comfort zone. After working with mac OS for more than 9 years, 
I’ll have to force myself to find other ways to stay productive.</li>
  <li>When I give courses, more than 95% of the people attending use Windows, and when they have
problems with their computers I’m not able to help. I want to change that.</li>
  <li>Learn other ways of doing things.</li>
  <li>I’ll feel I’m kinda cool 😎</li>
</ul>

<p>I plan to write a few posts about this topic. I want to share my experience about how I install and use Windows and Linux in a MacBook Pro 16’’ from 2019.</p>

<p>See you in the next post!</p>]]></content><author><name></name></author><category term="About this blog" /><summary type="html"><![CDATA[In March this year, we had the first big lockdown due to COVID-19. I used that extra time at home to rethink the way I use my computer to work. I decided to change it.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://0.0.0.0:4000/assets/images/cards/windows_linux_macos.jpg" /><media:content medium="image" url="http://0.0.0.0:4000/assets/images/cards/windows_linux_macos.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Running the script youtube-dl inside a docker container</title><link href="http://0.0.0.0:4000/2020/10/18/youtube-dl-using-docker.html" rel="alternate" type="text/html" title="Running the script youtube-dl inside a docker container" /><published>2020-10-18T11:30:00+02:00</published><updated>2020-10-18T11:30:00+02:00</updated><id>http://0.0.0.0:4000/2020/10/18/youtube-dl-using-docker</id><content type="html" xml:base="http://0.0.0.0:4000/2020/10/18/youtube-dl-using-docker.html"><![CDATA[<h2 id="tldr">TL;DR</h2>

<p>You don’t really need docker to run <code class="language-plaintext highlighter-rouge">youtube-dl</code>. You can just install it using <code class="language-plaintext highlighter-rouge">brew install youtube-dl</code> or 
<code class="language-plaintext highlighter-rouge">apt install youtube-dl</code> in Debian based distributions and you are ready to go. However, as I mentioned in a 
<a href="/2020/10/13/about-this-blog-docker.html">previous post</a>, I will use docker to run the command.</p>

<p>** Update 2020-10-21:** Upgraded the syntax highlight styles of the post. Improve the text to make the article
more readable.</p>

<h2 id="docker-concepts-involved">Docker concepts involved</h2>

<p>Running this <code class="language-plaintext highlighter-rouge">youtube-dl</code> inside docker, instead of installing it using our package manager, gives us a great opportunity to
learn and practice a few docker concepts. I’ve already talked about why I’m using docker as much as possible, even when it’s
overkill (like in this example). If you want to know about it, you can 
<a href="/2020/10/13/about-this-blog-docker.html">read about it in this post</a>.</p>

<p>If you follow the example, you will need to dig deeper into the following docker concepts:</p>

<ul>
  <li>Build custom docker images using a <code class="language-plaintext highlighter-rouge">Dockerfile</code></li>
  <li>Build arguments</li>
  <li>Differences between images and containers</li>
  <li>Run containers</li>
  <li>Bind volumes</li>
  <li>Bind volumes and file ownership/permissions</li>
</ul>

<p>Explaining these docker concepts is out of the scope of this post, so I will not go into the details of any of them.</p>

<h2 id="creating-a-dockerfile-for-docker-for-mac-how-we-want-to-build-our-image">Creating a Dockerfile for Docker for Mac: how we want to build our image</h2>

<p>As mentioned in the <a href="https://github.com/ytdl-org/youtube-dl">INSTALLATION section of the readme of the <code class="language-plaintext highlighter-rouge">youtube-dl</code> respository</a>,
installing the script is as simple as download it with <code class="language-plaintext highlighter-rouge">wget</code> or <code class="language-plaintext highlighter-rouge">curl</code>, set the execute permission and run it. 
All you need is a python interpreter and <code class="language-plaintext highlighter-rouge">ffmpeg</code> for certain options.</p>

<p>Instead, We will create a docker image and later use that image to run the command inside a container.</p>

<p>Let’s start by creating our working directory, <code class="language-plaintext highlighter-rouge">youtube-dl-docker</code>, in which will store all our files:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> <span class="nb">mkdir </span>youtube-dl-docker
<span class="o">&gt;</span> <span class="nb">cd </span>youtube-dl-docker
</code></pre></div></div>

<p>Make sure you are inside that folder. Open your favorite editor and create the following <code class="language-plaintext highlighter-rouge">Dockerfile</code>:</p>

<figure class="highlight"><pre><code class="language-docker" data-lang="docker"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="code"><pre><span class="k">FROM</span><span class="s"> python:3-alpine</span>

<span class="k">RUN  </span>apk add <span class="nt">--no-cache</span> ffmpeg curl <span class="o">&amp;&amp;</span> <span class="se">\
</span>     curl <span class="nt">-L</span> https://yt-dl.org/downloads/latest/youtube-dl <span class="nt">-o</span> /usr/local/bin/youtube-dl <span class="o">&amp;&amp;</span> <span class="se">\
</span>     <span class="nb">chmod </span>a+rx /usr/local/bin/youtube-dl

<span class="k">VOLUME</span><span class="s"> /downloads</span>
<span class="k">WORKDIR</span><span class="s"> /downloads</span>
<span class="k">ENTRYPOINT</span><span class="s"> ["youtube-dl"]</span>
</pre></td></tr></tbody></table></code></pre></figure>

<p>Let’s go through the <code class="language-plaintext highlighter-rouge">Dockerfile</code>:</p>

<div class="language-docker highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="s"> python:3-alpine</span>
</code></pre></div></div>

<p>Since <code class="language-plaintext highlighter-rouge">youtube-dl</code> needs python to run, we will create our image using the official python-3 image as the starting point.</p>

<div class="language-docker highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">RUN  </span>apk add <span class="nt">--no-cache</span> ffmpeg curl <span class="o">&amp;&amp;</span> <span class="se">\
</span>     curl <span class="nt">-L</span> https://yt-dl.org/downloads/latest/youtube-dl <span class="nt">-o</span> /usr/local/bin/youtube-dl <span class="o">&amp;&amp;</span> <span class="se">\
</span>     <span class="nb">chmod </span>a+rx /usr/local/bin/youtube-dl
</code></pre></div></div>

<p>The next step is to install the dependencies that we will need to run the script:</p>
<ul>
  <li><code class="language-plaintext highlighter-rouge">curl</code> to actually download the script</li>
  <li><code class="language-plaintext highlighter-rouge">ffmpeg</code> if we want to use certain options of <code class="language-plaintext highlighter-rouge">youtube-dl</code></li>
</ul>

<div class="language-docker highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">VOLUME</span><span class="s"> /downloads</span>
</code></pre></div></div>

<p>We declare the directory <code class="language-plaintext highlighter-rouge">/downloads</code> inside the container as a volume. We will use that mount point
to <a href="https://docs.docker.com/storage/bind-mounts/">bind mount</a> a directory from the operating system to store the 
downloaded video.</p>

<div class="language-docker highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">WORKDIR</span><span class="s"> /downloads</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">/downloads</code> will be our default working directory when we run any command inside a container created from this image.</p>

<div class="language-docker highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">ENTRYPOINT</span><span class="s"> ["youtube-dl"]</span>
</code></pre></div></div>

<p>We declare an <a href="https://docs.docker.com/engine/reference/builder/#entrypoint">ENTRYPOINT</a> so the container can be run as
an executable in case we need it.</p>

<h2 id="build-the-image">Build the image</h2>

<p>Once we have the <code class="language-plaintext highlighter-rouge">Dockerfile</code> in place, let’s build the image. We will build it using 
<a href="https://docs.docker.com/develop/develop-images/build_enhancements/">Builkit</a>, which is the enhanced way
to build images with docker.</p>

<p>Placed inside the <code class="language-plaintext highlighter-rouge">youtube-dl-docker</code> directory, we run the following commands to build the image:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>youtube-dl-docker <span class="o">&gt;</span> <span class="nb">export </span>DOCKER_BUILDKIT <span class="o">=</span> 1
youtube-dl-docker <span class="o">&gt;</span> docker image build <span class="nt">-t</span> youtube-dl <span class="nb">.</span>
</code></pre></div></div>

<p>The first command will tell docker to build the image using 
<a href="https://docs.docker.com/develop/develop-images/build_enhancements/"><code class="language-plaintext highlighter-rouge">buildkit</code></a>.</p>

<p>The second command is the one that will actually build the image. Depending on you internet connection, this command
may take a few minutes to run.</p>

<p>Once it finishes, and if everything went well, you should see a message like this:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>youtube-dl-docker <span class="o">&gt;</span> docker image build <span class="nt">-t</span> youtube-dl <span class="nb">.</span>
 ...
 ...
 <span class="o">=&gt;</span> <span class="o">=&gt;</span> exporting layers
 <span class="o">=&gt;</span> <span class="o">=&gt;</span> writing image sha256:9174386f3568131240a7c336cba55b6409f46398d549d26f3e131590f3ed37d6
 <span class="o">=&gt;</span> <span class="o">=&gt;</span> naming to docker.io/library/youtube-dl
</code></pre></div></div>

<p>With the option <code class="language-plaintext highlighter-rouge">-t</code> we set the tag of the image (<code class="language-plaintext highlighter-rouge">youtube-dl</code> in our case). This tag name will be used later to
run the container.</p>

<h2 id="running-the-command">Running the command</h2>

<p>Great! We have our image built and ready to be used. Let’s try it!</p>

<p>Go to <a href="https://www.youtube.com">www.youtube.com</a> and select a video. Once you have the URL of the video,
let’s download it by running the following command:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>youtube-dl-docker <span class="o">&gt;</span> docker run <span class="se">\</span>
                    <span class="nt">--rm</span> <span class="se">\ </span>
                    <span class="nt">-v</span> <span class="si">$(</span><span class="nb">pwd</span><span class="si">)</span>:/downloads <span class="se">\</span>
                    youtube-dl <span class="se">\</span>
                    <span class="s2">"https://www.youtube.com/watch?v=Q_F9CxSmGOM"</span>
</code></pre></div></div>

<p>This command creates a container based on the image with tag name <code class="language-plaintext highlighter-rouge">youtube-dl</code> (Remember? that is the one we created above). 
Docker will create a process, isolate it, assign resources like memory and CPU to it, make all the files that are in the image
available to this process, and run it.</p>

<p>The process will execute the command we defined in the <code class="language-plaintext highlighter-rouge">ENTRYPOINT</code> inside the <code class="language-plaintext highlighter-rouge">Dockerfile</code> and pass the right arguments to it.
The result is that the container will run the <code class="language-plaintext highlighter-rouge">youtube-dl</code> script and will download the video. One of the keys to understand
docker is to know the difference between a container and an image. There are plenty of resources on the internet which you can
<a href="https://www.google.com/search?q=containers+vs+images&amp;rlz=1C1CHBF_enES920ES920&amp;oq=containers+vs+images">search and browse</a> 
to understand the difference.</p>

<p>Once the download process finishes, you will end up with the downloaded video in the folder where you run the 
command (<code class="language-plaintext highlighter-rouge">youtube-dl-docker</code> in my case). And that’s it!!</p>

<p>Before continuing, let’s go through the different options of the <code class="language-plaintext highlighter-rouge">docker run</code> command:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">--rm</code> Remove the container after the execution finishes</li>
  <li><code class="language-plaintext highlighter-rouge">-v $(pwd):/downloads</code> use a bind mount, so the downloaded video ends up in the file system of the host computer
and not inside the container</li>
  <li><code class="language-plaintext highlighter-rouge">youtube-dl</code> is the tag name of the image we use to run the container. This tag name is the same one 
we passed to the <code class="language-plaintext highlighter-rouge">-t</code> option above when we built the image. Remember the 
command? If not, I will copy it here for your convenience: <code class="language-plaintext highlighter-rouge">docker image build -t youtube-dl .</code></li>
</ul>

<h2 id="the-problem-with-linux">The problem with Linux</h2>

<p>If you run this command in OSX after installing Docker for Mac, the owner of the downloaded file will be your system user.
However, if you follow the steps above in a Linux computer or inside WSL2 in Windows 10, you will end up with the 
downloaded files belonging to the root user:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>youtube-dl-docker <span class="o">&gt;</span> <span class="nb">ls</span> <span class="nt">-l</span>
<span class="nt">-rw-r--r--</span> 1 root root 3097969115 Jan  7  2020 the_video.mp4
</code></pre></div></div>

<p>This is due to how the filesystem is handled in Docker for Mac vs how it is handled in Linux.</p>

<h2 id="fixing-the-file-permissions-in-linux">Fixing the file permissions in Linux</h2>

<p>To fix this issue, we are going to change the <code class="language-plaintext highlighter-rouge">Dockerfile</code> a little bit. We will pass the user id at build time 
as a build argument and create a user inside the container with the same user id as our system user. Then, we will run
the process inside the container as that user instead of root.</p>

<p>Let’s modify the <code class="language-plaintext highlighter-rouge">Dockerfile</code>:</p>

<div class="language-docker highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="s"> python:3-alpine</span>

<span class="k">ARG</span><span class="s"> USERID</span>

<span class="k">RUN  </span>apk add <span class="nt">--no-cache</span> ffmpeg curl <span class="o">&amp;&amp;</span> <span class="se">\
</span>     curl <span class="nt">-L</span> https://yt-dl.org/downloads/latest/youtube-dl <span class="nt">-o</span> /usr/local/bin/youtube-dl <span class="o">&amp;&amp;</span> <span class="se">\
</span>     <span class="nb">chmod </span>a+rx /usr/local/bin/youtube-dl <span class="o">&amp;&amp;</span> <span class="se">\
</span>     adduser <span class="nt">--uid</span> <span class="nv">$USERID</span> <span class="nt">--gecos</span> <span class="s2">""</span> <span class="nt">--disabled-password</span> youtubedl

<span class="k">VOLUME</span><span class="s"> /downloads</span>
<span class="k">WORKDIR</span><span class="s"> /downloads</span>

<span class="k">USER</span><span class="s"> youtubedl</span>

<span class="k">ENTRYPOINT</span><span class="s"> ["youtube-dl"]</span>
</code></pre></div></div>

<p>The differences, compared to the previous <code class="language-plaintext highlighter-rouge">Dockerfile</code> are the following:</p>

<div class="language-docker highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">ARG</span><span class="s"> USERID</span>
</code></pre></div></div>
<p>We define a build argument that will be the user id in the host system. We want to create a user inside the container
with the same user id as the system user and runs the process using that user.</p>

<div class="language-docker highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">RUN  </span>apk add <span class="nt">--no-cache</span> ffmpeg curl <span class="o">&amp;&amp;</span> <span class="se">\
</span>     curl <span class="nt">-L</span> https://yt-dl.org/downloads/latest/youtube-dl <span class="nt">-o</span> /usr/local/bin/youtube-dl <span class="o">&amp;&amp;</span> <span class="se">\
</span>     <span class="nb">chmod </span>a+rx /usr/local/bin/youtube-dl <span class="o">&amp;&amp;</span> <span class="se">\
</span>     adduser <span class="nt">--uid</span> <span class="nv">$USERID</span> <span class="nt">--gecos</span> <span class="s2">""</span> <span class="nt">--disabled-password</span> youtubedl
</code></pre></div></div>

<p>Here, we add a command to create a user called <code class="language-plaintext highlighter-rouge">youtubedl</code> with the same id as our system user.</p>

<div class="language-docker highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">USER</span><span class="s"> youtubedl</span>
</code></pre></div></div>

<p>We include in our image a small “annotation” saying that by default, the process that run using this image, should run
as user <code class="language-plaintext highlighter-rouge">youtubedl</code>.</p>

<p>Let`s build the image passing the user id as a build argument:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>youtube-dl-docker <span class="o">&gt;</span> <span class="nb">export </span>DOCKER_BUILDKIT <span class="o">=</span> 1
youtube-dl-docker <span class="o">&gt;</span> docker image build <span class="nt">-t</span> youtube-dl <span class="nt">--build-arg</span> <span class="nv">USERID</span><span class="o">=</span><span class="si">$(</span><span class="nb">id</span> <span class="nt">-u</span><span class="si">)</span> <span class="nb">.</span>
</code></pre></div></div>

<p>After doing this, running the same command we run before will download the files inside the container
as the user <code class="language-plaintext highlighter-rouge">youtubedl</code>. Since that user has the same user id as our system user, the files
will end up belonging to us:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>youtube-dl-docker <span class="o">&gt;</span> docker run <span class="nt">--rm</span> <span class="nt">-v</span> <span class="si">$(</span><span class="nb">pwd</span><span class="si">)</span>:/downloads youtube-dl <span class="s2">"https://www.youtube.com/watch?v=Q_F9CxSmGOM"</span>
youtube-dl-docker <span class="o">&gt;</span> <span class="nb">ls</span> <span class="nt">-l</span>
<span class="nt">-rw-r--r--</span> 1 youruser youruser 3097969115 Jan  7  2020 the_video.mp4
</code></pre></div></div>

<h2 id="improvements">Improvements</h2>

<h3 id="create-a-makefile-to-help-you-run-the-commands">Create a <code class="language-plaintext highlighter-rouge">makefile</code> to help you run the commands</h3>

<p>We can make our life easier by creating a <code class="language-plaintext highlighter-rouge">Makefile</code> to build the image or remove it:</p>

<pre><code class="language-Makefile">USERID := $(shell id -u)

build: export DOCKER_BUILDKIT = 1
build:
        docker image build --build-arg USERID=$(USERID) -t youtube-dl .

clean:
        docker image rm youtube-dl
</code></pre>

<p>To build the container, we can just run:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>youtube-dl-docker <span class="o">&gt;</span> make build
</code></pre></div></div>

<p>We can remove the image by running:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>youtube-dl-docker <span class="o">&gt;</span> make clean
</code></pre></div></div>

<h3 id="create-an-alias">Create an alias</h3>

<p>We can also create an alias to avoid writing this very long command every time we want to :</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> <span class="nb">alias </span>youtube-dl<span class="o">=</span><span class="s2">"docker run --rm -v /Users/aalba/MyStuff/youtube-dl:/downloads youtube-dl"</span>
</code></pre></div></div>

<p>Once you define this alias, running the command <code class="language-plaintext highlighter-rouge">youtube-dl "https://www.youtube.com/watch?v=Q_F9CxSmGOM"</code> will
download the file to the current folder.</p>

<h2 id="limitations">Limitations</h2>

<p>Since we are running the command inside a container, those options from the <code class="language-plaintext highlighter-rouge">youtube-dl</code> command that
read or write files to the host file system, might require the definition of extra bind volumes to work properly.</p>

<h2 id="references">References</h2>

<p>The code of the post can be found in <a href="https://github.com/alfonsoalba/youtube-dl-docker">this git repository</a>.</p>]]></content><author><name></name></author><category term="Docker" /><summary type="html"><![CDATA[youtube-dl is a python script that lets you download YouTube videos. In this post I'll tell what I did to run the script from inside a docker container.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://0.0.0.0:4000/assets/images/cards/touch_play_button.jpg" /><media:content medium="image" url="http://0.0.0.0:4000/assets/images/cards/touch_play_button.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>