<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="http://erick.matsen.org/feed.xml" rel="self" type="application/atom+xml" /><link href="http://erick.matsen.org/" rel="alternate" type="text/html" /><updated>2024-11-20T10:35:50+00:00</updated><id>http://erick.matsen.org/feed.xml</id><title type="html">Erick Matsen</title><subtitle>I wanted you to know about this.</subtitle><author><name>Erick Matsen</name><email>matsen@fredhutch.org</email></author><entry><title type="html">Second-generation interactive shell tools</title><link href="http://erick.matsen.org/2020/01/04/2nd-gen-interactive-shell.html" rel="alternate" type="text/html" title="Second-generation interactive shell tools" /><published>2020-01-04T00:00:00+00:00</published><updated>2020-01-04T00:00:00+00:00</updated><id>http://erick.matsen.org/2020/01/04/2nd-gen-interactive-shell</id><content type="html" xml:base="http://erick.matsen.org/2020/01/04/2nd-gen-interactive-shell.html"><![CDATA[<h2 id="summary">Summary</h2>

<ul>
  <li>rather than naked shell, <a href="#tmux">try</a> <code class="language-plaintext highlighter-rouge">tmux</code> for an organized collection of shells</li>
  <li>rather than local file system navigation, <a href="#fzf">try</a> <code class="language-plaintext highlighter-rouge">fzf</code> to fuzzily get what you want</li>
  <li>rather than <code class="language-plaintext highlighter-rouge">cd</code>, <a href="#autojump">try</a> <code class="language-plaintext highlighter-rouge">autojump</code>, which remembers your favorite locations</li>
  <li>rather than <code class="language-plaintext highlighter-rouge">find</code>, <a href="#fd">try</a> <code class="language-plaintext highlighter-rouge">fd</code>, which has a much cleaner interface</li>
  <li>rather than <code class="language-plaintext highlighter-rouge">grep</code>, <a href="#ag">try</a> <code class="language-plaintext highlighter-rouge">ag</code>, which is simpler and faster</li>
</ul>

<h2 id="introduction">Introduction</h2>

<p>The built-in shell environment is tremendously productive, but “second generation” tools can make it even more so.</p>

<p>If you want to try out these tools, you can run</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run -it quay.io/matsen/cozy-demo
</code></pre></div></div>

<p>to test them out in Docker.
There is some funkiness with the display on this Docker image (tmux pane splits use hashes and q’s) but it’s enough to get the idea.
See <a href="https://github.com/matsen/cozy-demo">the corresponding GitHub repository</a> if you want to mimic the configuration.</p>

<h1 id="tmux">tmux</h1>

<p><img src="/public/images/goodnight-shell.png" alt="" /></p>

<p>When working on a modern desktop computer, it’s easy to arrange multiple windows side by side, to switch between applications, etc.
On the command line this is achieved by use of a “terminal multiplexer”.
This is especially lovely for working on remote machines, where one can detach and re-attach sessions with all their attendant windows.</p>

<p>The two most popular terminal multiplexers are <a href="https://www.gnu.org/software/screen/manual/screen.html">GNU Screen</a> and <a href="https://github.com/tmux/tmux/wiki">tmux</a>.
This tutorial will demonstrate <code class="language-plaintext highlighter-rouge">tmux</code>, which is newer and more feature-rich.
<strong>Note that the example commands below use the configuration described in the above-mentioned GitHub repository.</strong>
This configuration is vi-keybinding oriented (emacs-style keybindings are also available).</p>

<p>You send commands to these programs using a “prefix” key command followed by another keystroke.
In our configuration, the prefix is <code class="language-plaintext highlighter-rouge">Ctrl-a</code>, the most easily typed control combination.
For example, hitting <code class="language-plaintext highlighter-rouge">Ctrl-a</code>, letting go, then typing <code class="language-plaintext highlighter-rouge">c</code> opens a new terminal window.
We will abbreviate this combination <code class="language-plaintext highlighter-rouge">Ctrl-a c</code>.</p>

<h3 id="first-steps-in-tmux">First steps in tmux</h3>

<p>As a first step, I suggest just starting up tmux by executing <code class="language-plaintext highlighter-rouge">tmux</code> and trying out the following commands.
Open a few windows, type some commands, cycle through them, open a few panes in a single window, and move around through them.</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Ctrl-a c</code>   New window</li>
  <li><code class="language-plaintext highlighter-rouge">Ctrl-a &lt;Space&gt;</code>   Next window</li>
  <li><code class="language-plaintext highlighter-rouge">Ctrl-a NUMBER</code>   Move to window number <code class="language-plaintext highlighter-rouge">NUMBER</code></li>
  <li><code class="language-plaintext highlighter-rouge">Ctrl-a Ctrl-a</code>   Move to the previously visited window</li>
  <li><code class="language-plaintext highlighter-rouge">Ctrl-a v</code>   Split window into panes vertically</li>
  <li><code class="language-plaintext highlighter-rouge">Ctrl-a s</code>   Split window into panes horizontally</li>
  <li><code class="language-plaintext highlighter-rouge">Ctrl-a &lt;arrow&gt;</code>   Move between panes</li>
  <li><code class="language-plaintext highlighter-rouge">Ctrl-d</code>   Close a pane or window (works outside of tmux)</li>
  <li><code class="language-plaintext highlighter-rouge">Ctrl-a q</code>   Kill a pane or window</li>
</ul>

<p>Hopefully it’s clear that the windows are complete environments which are indexed at the bottom, and the panes are sub-windows that have no associated indicator.
Each pane has a shell running inside of it.</p>

<p>The following commands are also essential:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Ctrl-a ?</code>   Command help. Note that <code class="language-plaintext highlighter-rouge">/</code> will allow you to search.</li>
  <li><code class="language-plaintext highlighter-rouge">Ctrl-a a</code>   Actually send a <code class="language-plaintext highlighter-rouge">Ctrl-a</code> to the terminal (e.g. to move to the beginning of the line when editing a shell command)</li>
</ul>

<h3 id="detaching-and-attaching-a-session">Detaching and attaching a session</h3>

<p>Tmux can keep your window configuration and all of the running processes going, even if your terminal goes away.
This is called detaching and reattaching.</p>

<p>The polite way to detach and reattach is to (try this!)</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Ctrl-a d</code>   Detach</li>
  <li><code class="language-plaintext highlighter-rouge">tmux attach</code>   Reattach</li>
</ul>

<p>Reattaching is also very handy if you are moving between laptops.
You can detach your remote session from one, log in on another, and then reattach on the second laptop.
Note that if you have different size terminal windows between the two machines the session can look wonky.
In that case simply use <code class="language-plaintext highlighter-rouge">tmux attach -d</code>, which will “steal” the session from any other login shell and redraw the session from scratch.</p>

<p>If your wifi drops you might just get disconnected to a remote machine, in which case you wouldn’t have the opportunity to politely detach.
<em>That’s fine.</em>
In that case, just re-connect to your remote machine, and <code class="language-plaintext highlighter-rouge">tmux attach</code>.</p>

<p>Also note that <code class="language-plaintext highlighter-rouge">tmux attach</code> will fail if you don’t have a session open already; if that happens just enter <code class="language-plaintext highlighter-rouge">tmux</code> to start a new session.</p>

<h3 id="resizing-panes">Resizing panes</h3>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Ctrl-a &lt;</code>   Move vertical split left</li>
  <li><code class="language-plaintext highlighter-rouge">Ctrl-a &gt;</code>   Move vertical split right</li>
  <li><code class="language-plaintext highlighter-rouge">Ctrl-a +</code>   Move horizontal split up</li>
  <li><code class="language-plaintext highlighter-rouge">Ctrl-a =</code>   Move horizontal split down</li>
</ul>

<p>Note: You can hit <code class="language-plaintext highlighter-rouge">Ctrl-a</code> once, and press the resize operator a number of times (or hold it) to do larger resizes.
For example, try <code class="language-plaintext highlighter-rouge">Ctrl-a &lt;&lt;&lt;&lt;</code>.</p>

<h3 id="naming--finding-windows">Naming &amp; finding windows</h3>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Ctrl-a ,</code>   Name a window</li>
  <li><code class="language-plaintext highlighter-rouge">Ctrl-a '</code>   Present a list of windows (by name), which you can choose from by <code class="language-plaintext highlighter-rouge">&lt;arrow&gt;</code> and <code class="language-plaintext highlighter-rouge">Enter</code> to switch to a window</li>
  <li><code class="language-plaintext highlighter-rouge">Ctrl-a &lt;numeric&gt;</code>   Switch to a window by number.</li>
</ul>

<h3 id="scrolling-and-copypaste-in-tmux">Scrolling and copy/paste in tmux</h3>

<p>When you have lots of output, it’s nice to be able to scroll up and down through history.
Pressing <code class="language-plaintext highlighter-rouge">Ctrl-a [</code> will place you in scroll mode.
Use can now use arrow keys, PageUp/PageDown keys, or <code class="language-plaintext highlighter-rouge">Ctrl-u</code>/<code class="language-plaintext highlighter-rouge">Ctrl-d</code> to scroll through the history.
You can search up through history with <code class="language-plaintext highlighter-rouge">?</code> and down with <code class="language-plaintext highlighter-rouge">/</code>.</p>

<p>From this mode, you can also press <code class="language-plaintext highlighter-rouge">Space</code> to enter copy-mode, arrow keys to specify a selection range, and <code class="language-plaintext highlighter-rouge">Enter</code> to copy the selection.
To paste the selection, use <code class="language-plaintext highlighter-rouge">Ctrl-a ]</code>.</p>

<p>Note: if you are running tmux 2.1 or greater (check with <code class="language-plaintext highlighter-rouge">tmux -V</code>) and you really like your mouse scroll wheel, you can try <code class="language-plaintext highlighter-rouge">set -g mouse on</code> which will drop you into tmux scroll mode when you use the scroll wheel.
But I suggest keeping your fingers on the keyboard and ignoring the “rat”.</p>

<h3 id="advanced-tmux-tips">Advanced tmux tips</h3>

<p>You can also use <code class="language-plaintext highlighter-rouge">h</code>, <code class="language-plaintext highlighter-rouge">j</code>, <code class="language-plaintext highlighter-rouge">k</code>, and <code class="language-plaintext highlighter-rouge">l</code> in place of the arrow keys, as in <code class="language-plaintext highlighter-rouge">vim</code>.</p>

<p>If you want to pipe to the tmux copy/paste buffer, try this alias:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>alias tc='tmux loadb -'
</code></pre></div></div>

<p>With this alias I can, for example, <code class="language-plaintext highlighter-rouge">ls /very/long/path.txt | tc</code> and then paste rather than having to do an explicit select and copy.</p>

<p>There are many more commands than we have connected with command sequences.
See the tmux man page for a complete list.
<code class="language-plaintext highlighter-rouge">Ctrl-a :</code> will put you at a prompt that will allow you to execute an arbitrary command.</p>

<h3 id="using-tmux-with-ssh">Using tmux with ssh</h3>

<p>Ssh keys in a long-running tmux session require some care.
I use <a href="https://www.funtoo.org/Keychain">keychain</a> to automate <code class="language-plaintext highlighter-rouge">ssh-agent</code> and friends together with tmux like so:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>eval `keychain --eval id_rsa` &amp;&amp; tmux attach -d
</code></pre></div></div>

<p>Sometimes it seems that ssh connections inside those sessions need a little refresh.
For that I use <a href="https://gist.github.com/matsen/b121999f861cd001c6814a20d032fa53">this shell command</a>.</p>

<h3 id="you-can-get-fancy-with-tmux">You can get fancy with tmux</h3>

<p>Some people love complex tmux setups with tons of sub-panes with things running in them.
I prefer to have a simple setup where things can get set up and torn down easily.
If you like a fancy setup, you can use something like <a href="https://github.com/tmux-plugins/tmux-resurrect">tmux-resurrect</a> to maintain such setups on your local machine between reboots.
There are also tmux plugin managers, etc, but just a simple configuration file is all I’ve needed.</p>

<p>You can also have lots of different detached sessions, give them names, etc.
I used to do this, with each session corresponding to a single project.
For me it ended up being too much work to manage, but perhaps it fits for you.</p>

<h1 id="fzf">fzf</h1>

<p>We all love tab completion for matching files.
But sometimes it too can be painful.
Consider this list of files, which is some the various posts that have appeared on <a href="http://fredhutch.io">fredhutch.io</a>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2014-04-24-command-line.md        2015-04-06-R-sequence-analysis.md
2014-04-27-terminal-multiplex.md  2015-04-23-ucsc-xena-workshop.md
2014-05-09-galaxy.md              2015-04-24-third-galaxy-101.md
2014-05-11-editing.md             2015-05-04-spring-2015-r.md
2014-05-17-git.md                 2015-05-20-spring-2015-unix-hpc.md
2014-05-20-R.md                   2015-06-03-summer-bioinfo.md
2014-07-13-toolbox.md             2015-08-26-data-center-tour.md
2014-07-16-aws.md                 2015-08-27-cloud-computing.md
2014-08-13-synapse.md             2015-12-02-galaxy-rna-seq.md
2014-09-19-R-course.md            2016-01-21-cds-git.md
2014-10-20-introductory-r-.md     2016-02-25-immunespace.md
2014-10-22-shippable.md           2016-03-01-i-heart-pandas.md
2014-10-27-bioconductor.md        2016-03-28-gizmo-brownbag.md
2014-11-03-labkey.md              2016-06-14-scicomp-unix-hpc.md
2014-11-07-intermediate-R.md      2016-07-28-galaxy-101.md
2014-12-09-hidra.md               2016-08-23-chipseq-class.md
2014-12-15-scicomp-unix-hpc.md    2016-08-31-rnaseq-class.md
2015-02-11-scicomp-unix-hpc.md    2016-09-08-inkscape.md
2015-03-09-introductory-R.md      2016-09-27-intro-r.md
2015-03-12-rollout-galaxy-101.md  2016-10-03-introbio.md
2015-04-02-april-galaxy-101.md    2017-01-05-command-line-cozy.md
</code></pre></div></div>

<p>to tab-complete through these files is a pain, because I actually want to choose a file by the text portion of the descriptor, which is past the date.</p>

<p>What we really want is <em>fuzzy finding</em>, which allows us to match via partial substring matches.
By this I mean that arbitrary substrings of your query string get matched with arbitrary substrings of your target strings, in an ordered fashion (see below for an example).
This is available <a href="https://github.com/blog/793-introducing-the-file-finder">as part of the GitHub web interface</a> for finding files, and actually even in the Chrome address bar.</p>

<p><a href="https://github.com/junegunn/fzf">fzf</a> brings fuzzy finding to the shell.
For example, if I want the posts from 2015 that contain <code class="language-plaintext highlighter-rouge">galaxy</code>, I could type <code class="language-plaintext highlighter-rouge">2015galaxy</code>, and 💥:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  2015-03-12-rollout-galaxy-101.md
  2015-04-24-third-galaxy-101.md
  2015-04-02-april-galaxy-101.md
  2014-05-09-galaxy.md
&gt; 2015-12-02-galaxy-rna-seq.md
  5/41
&gt; 2015galaxy
</code></pre></div></div>

<p>we get a list of the files that contain <code class="language-plaintext highlighter-rouge">201</code> <code class="language-plaintext highlighter-rouge">5</code> and <code class="language-plaintext highlighter-rouge">galaxy</code>.
In fact, I could have typed <code class="language-plaintext highlighter-rouge">15axy</code> and gotten the same result, because it matches the same set of files.
I can then type more characters to limit the search, or use the arrow keys to move between results, and then return to select.
Note that there is a “false positive” here, namely <code class="language-plaintext highlighter-rouge">2014-05-09-galaxy.md</code> which is from 2014 but has a 5 in the month.
That’s just a natural consequence of the way fuzzy finding works.</p>

<p>Here are two very easy ways to get started with fzf from the shell:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">Ctrl-t</code> drops you into the fzf fuzzy command completer from the shell prompt. For example, <code class="language-plaintext highlighter-rouge">less Ctrl-t</code> will allow you to find the file that you want to <code class="language-plaintext highlighter-rouge">less</code>.</li>
  <li><code class="language-plaintext highlighter-rouge">Ctrl-r</code>, the usual reverse search on your history, gets replaced by the fuzzified version. This is great and allows you to be vague about what you’re looking for.</li>
</ul>

<p>For many more uses, including vim integration, see the <a href="https://github.com/junegunn/fzf">fzf</a> GitHub page.</p>

<h1 id="autojump">autojump</h1>

<p><a href="https://github.com/wting/autojump">autojump</a> is a <code class="language-plaintext highlighter-rouge">cd</code> replacement that learns.
It keeps a record of where you have been in the past, and given multiple directories that match an argument, chooses the one that you have been in the most.
The autojump command is <code class="language-plaintext highlighter-rouge">j</code>.</p>

<p>For example, I frequently visit our data directory on the shared filesystem at <code class="language-plaintext highlighter-rouge">/fh/fast/matsen_e/data</code>.
Instead of typing out that full filename I can just type <code class="language-plaintext highlighter-rouge">j data</code> to <code class="language-plaintext highlighter-rouge">cd</code> there.</p>

<p>This works with partial filenames as well.
In the example Docker container I have a few directories that have cats in them, and <code class="language-plaintext highlighter-rouge">j cats</code> will take you to <code class="language-plaintext highlighter-rouge">/root/cats/siamese-mostpopular</code>, which is the most popular subdirectory containing <code class="language-plaintext highlighter-rouge">cats</code>.</p>

<p>If you are in the most popular directory with a certain string, then running the same <code class="language-plaintext highlighter-rouge">j</code> command will take you to the second most popular.
For example, repeating <code class="language-plaintext highlighter-rouge">j cats</code> a second time would take us to <code class="language-plaintext highlighter-rouge">/root/cats/bengal</code>, which is the subdirectory we’ve visited the second most.
(We could have used <code class="language-plaintext highlighter-rouge">cat</code> here as the argument to <code class="language-plaintext highlighter-rouge">j</code> instead of <code class="language-plaintext highlighter-rouge">cats</code> but I didn’t want to confuse things with the <code class="language-plaintext highlighter-rouge">cat</code> shell command.)</p>

<p>The information about what directories you spend the most time in is stored in <code class="language-plaintext highlighter-rouge">~/.local/share/autojump/autojump.txt</code>.
For our example Docker image it looks like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>50.0	/root/cats/siamese-mostpopular
30.0	/root/cats/siamese-lesspopular
40.0	/root/cats/bengal
10.0	/root/work
</code></pre></div></div>

<p>The left-hand column here is the popularity index and the right hand is the directory.</p>

<h1 id="fd">fd</h1>

<p><a href="https://www.gnu.org/software/findutils/manual/html_mono/find.html">GNU Find</a> is incredibly powerful, but has an annoying interface.
The <code class="language-plaintext highlighter-rouge">fd</code> command fixes that.</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">fd PATTERN</code> finds a file in the current directory whose filename matches <code class="language-plaintext highlighter-rouge">PATTERN</code></li>
  <li><code class="language-plaintext highlighter-rouge">fd PATTERN -x COMMAND</code> executes <code class="language-plaintext highlighter-rouge">COMMAND</code> for each file matching <code class="language-plaintext highlighter-rouge">PATTERN</code></li>
</ul>

<p>For example, <code class="language-plaintext highlighter-rouge">fd .txt -x head -n 1</code> will perform <code class="language-plaintext highlighter-rouge">head -n 1</code> on each file matching <code class="language-plaintext highlighter-rouge">.txt</code>.</p>

<p>The <code class="language-plaintext highlighter-rouge">fd</code> command has sensible defaults for the modern environment, such as ignoring <code class="language-plaintext highlighter-rouge">.git</code> directories and files that appear in <code class="language-plaintext highlighter-rouge">.gitignore</code>.
See the <a href="https://github.com/sharkdp/fd">fd GitHub page</a> for more examples and detail.</p>

<h1 id="ag">ag</h1>

<p><code class="language-plaintext highlighter-rouge">ag</code> is a faster replacement for <code class="language-plaintext highlighter-rouge">grep</code> that has a nicer input and output interface.</p>

<p>For example, as I’m writing this I might look for instances of the word “fancy” in the repository for this blog.
Using <code class="language-plaintext highlighter-rouge">ag fancy</code> gets me:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>_posts/2019-11-05-travis.md
22:It can do all sorts of fancy pipelines, but here we'll just get it to do one simple thing: run a command in a Docker container.

_posts/2019-12-31-command-line-cozy.md
145:### You can get fancy with tmux
149:If you like a fancy setup, you can use something like [tmux-resurrect](https://github.com/tmux-plugins/tmux-resurrect) to maintain such setups on your local machine between reboots.
</code></pre></div></div>

<p>which shows clearly that this word appeared once in a post about Travis, and twice in this file (before I wrote this section).</p>

<p>In contrast, we can try the same thing using <code class="language-plaintext highlighter-rouge">grep -r fancy</code></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>_posts/2019-11-05-travis.md:It can do all sorts of fancy pipelines, but here we'll just get it to do one simple thing: run a command in a Docker container.
_posts/2019-12-31-command-line-cozy.md:### You can get fancy with tmux
_posts/2019-12-31-command-line-cozy.md:If you like a fancy setup, you can use something like [tmux-resurrect](https://github.com/tmux-plugins/tmux-resurrect) to maintain such setups on your local machine between reboots.
_site/feed.xml:&amp;lt;h3 id=&amp;quot;you-can-get-fancy-with-tmux&amp;quot;&amp;gt;You can get fancy with tmux&amp;lt;/h3&amp;gt;
_site/feed.xml:If you like a fancy setup, you can use something like &amp;lt;a href=&amp;quot;https://github.com/tmux-plugins/tmux-resurrect&amp;quot;&amp;gt;tmux-resurrect&amp;lt;/a&amp;gt; to maintain such setups on your local machine between reboots.
_site/feed.xml:It can do all sorts of fancy pipelines, but here we’ll just get it to do one simple thing: run a command in a Docker container.&amp;lt;/p&amp;gt;
_site/2019/11/05/travis.html:It can do all sorts of fancy pipelines, but here we’ll just get it to do one simple thing: run a command in a Docker container.&lt;/p&gt;
_site/articles/2019/12/31/command-line-cozy.html:&lt;h3 id="you-can-get-fancy-with-tmux"&gt;You can get fancy with tmux&lt;/h3&gt;
_site/articles/2019/12/31/command-line-cozy.html:If you like a fancy setup, you can use something like &lt;a href="https://github.com/tmux-plugins/tmux-resurrect"&gt;tmux-resurrect&lt;/a&gt; to maintain such setups on your local machine between reboots.
</code></pre></div></div>

<p>First, things aren’t as nicely organized into sections.
Second, grep looks into all of the files, including the ones that are in <code class="language-plaintext highlighter-rouge">.gitignore</code>.
The git-ignored files in <code class="language-plaintext highlighter-rouge">_site</code> are auto-generated and they only add noise to my search.</p>

<p><code class="language-plaintext highlighter-rouge">ag</code> is also very fast and has lots of nice editor integrations.
See the <a href="https://github.com/ggreer/the_silver_searcher">ag GitHub page</a> for more details.</p>

<h1 id="conclusion-and-future-directions">Conclusion and future directions</h1>

<p>We’ve looked at a few tools that can help make it easier to interact with the shell.</p>

<p>We haven’t considered the shell itself.
If you spend a lot of time in the shell, you might consider <a href="https://en.wikipedia.org/wiki/Z_shell">zsh</a> or <a href="https://fishshell.com/">fish</a>.</p>

<p>Or you could just be happy with the defaults.
Every minute you spend tweaking your configuration is a minute you aren’t doing creative and beautiful things.</p>

<hr />

<p>Thank you to <a href="https://github.com/metasoarous">Chris Small</a> and <a href="https://github.com/cmccoy">Connor McCoy</a>, two previous group members who pointed me to some of these tools.
Chris wrote parts of the section on tmux as part of his course material for <a href="http://fredhutch.io">fredhutch.io</a>.</p>]]></content><author><name>Erick Matsen</name><email>matsen@fredhutch.org</email></author><summary type="html"><![CDATA[Modern replacements for classic interactive shell commands.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://erick.matsen.org/goodnight-shell.png" /><media:content medium="image" url="http://erick.matsen.org/goodnight-shell.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Trivial Travis testing via Docker</title><link href="http://erick.matsen.org/2019/11/05/travis.html" rel="alternate" type="text/html" title="Trivial Travis testing via Docker" /><published>2019-11-05T00:00:00+00:00</published><updated>2019-11-05T00:00:00+00:00</updated><id>http://erick.matsen.org/2019/11/05/travis</id><content type="html" xml:base="http://erick.matsen.org/2019/11/05/travis.html"><![CDATA[<p>In this post I’ll show how to use the <a href="https://travis-ci.org/">Travis</a> continuous-integration system to test a project via Docker.
This is <em>not</em> a description of building Docker containers using Travis.
Specifically, the setup is:</p>

<ul>
  <li>you have a project on GitHub which contains a Dockerfile specifying how to install your project’s dependencies into a Docker image</li>
  <li>you have some means of building and testing that project within the corresponding Docker container</li>
  <li>you would like this build/test to happen automatically on push.</li>
</ul>

<p>For background on how to set up the first two steps, see <a href="/2018/04/19/docker.html">a previous post about Docker</a>.</p>

<h2 id="travis">Travis</h2>

<p>Travis is an industry-standard continuous-integration (CI) service that is nicely integrated with GitHub.
It can do all sorts of fancy pipelines, but here we’ll just get it to do one simple thing: run a command in a Docker container.</p>

<p>To demonstrate we’ll use the same <a href="https://github.com/matsen/cowsay-test">cowsay-test</a> repository as in the <a href="/2018/04/19/docker.html">previous post</a>.
This time let’s look at the <code class="language-plaintext highlighter-rouge">.travis.yml</code> file in that repository, which is in <a href="https://en.wikipedia.org/wiki/YAML">YAML</a> format:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">language</span><span class="pi">:</span> <span class="s">minimal</span>

<span class="na">services</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">docker</span>

<span class="na">script</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">docker run -t matsen/cowsay-test /bin/bash -c "cowsay hello"</span>
</code></pre></div></div>

<p>Travis has all sorts of preconfigured machines to run code in a variety of languages.
In the first line, we are specifying that we want to use a “minimal” machine, which isn’t designed for a specific language.
This image just has <a href="https://docs.travis-ci.com/user/languages/minimal-and-generic/">a collection of standard tools, including Docker</a>.
The second block specifies that we would like to use Docker.</p>

<p>The third and final block specifies what command we’d like Travis to run.
It runs <code class="language-plaintext highlighter-rouge">cowsay hello</code> via bash inside the <code class="language-plaintext highlighter-rouge">matsen/cowsay-test</code> Docker image from Docker Hub.</p>

<p>We can test this locally with a local clone of the repository.
If we want to test the whole process, we can execute the</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker build -t matsen/cowsay-test .
</code></pre></div></div>

<p>command inside the repository, and then copy and paste the <code class="language-plaintext highlighter-rouge">docker run</code> command from the <code class="language-plaintext highlighter-rouge">.travis.yml</code> file (without the leading <code class="language-plaintext highlighter-rouge">-</code>):</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run -t matsen/cowsay-test /bin/bash -c "cowsay hello"
</code></pre></div></div>

<p>If we want to test the version that’s on Docker Hub, we can instead do <code class="language-plaintext highlighter-rouge">docker pull -t matsen/cowsay-test</code> rather than the above <code class="language-plaintext highlighter-rouge">docker build</code> command.</p>

<p>To automate this process for any repository, we just need these files committed to our repository, and to:</p>

<ul>
  <li>set up an automated build on Docker Hub <a href="/2018/04/19/docker.html#automated-builds">as described previously</a></li>
  <li>make an account on <a href="https://travis-ci.org">travis-ci.org</a>, signing in via your GitHub account</li>
  <li>go to <a href="https://travis-ci.org/account/repositories">https://travis-ci.org/account/repositories</a> and find the repository you want to test</li>
  <li>click on the switch to activate testing of your repository</li>
  <li>click on the hamburger menu button (☰) in the upper right, and select “Trigger build” using the default values</li>
  <li>add a Travis badge (see the cowsay-test repository for an example that makes the badge a link to your build page)</li>
</ul>

<h2 id="using-quayio">Using quay.io</h2>

<p>Docker-the-software is great and very popular.
Docker-the-company is <a href="https://www.zdnet.com/article/docker-is-in-deep-trouble/">struggling somewhat</a> and it shows in that Docker Hub is slow and sometimes unreliable.
<a href="https://quay.io/">quay.io</a> is an alternative service that we are starting to use.</p>

<p>If you want to try it out, fork the <a href="https://github.com/matsen/cowsay-test">cowsay-test</a> repository on GitHub then:</p>

<ul>
  <li>make an account on <a href="https://quay.io/">quay.io</a></li>
  <li>click on “Create New Repository” on the upper right of your user page</li>
  <li>name your repository in the text box</li>
  <li>Follow the prompts, selecting
    <ul>
      <li>Public repository</li>
      <li>Link to a GitHub Repository Push</li>
      <li>Select your forked repository</li>
      <li>your Dockerfile is <code class="language-plaintext highlighter-rouge">/Dockerfile</code> as per the help on the right hand side</li>
      <li>your build context is just <code class="language-plaintext highlighter-rouge">/</code> as per the help on the right hand side</li>
    </ul>
  </li>
  <li>Continue to the page for your build</li>
  <li>Select “Start New Build”</li>
</ul>

<p>On the Travis side, all you need to do is to add <code class="language-plaintext highlighter-rouge">quay.io/</code> to the start of your Docker image name, like so:</p>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">script</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">docker run -t quay.io/matsen/cowsay-test /bin/bash -c "cowsay hello"</span>
</code></pre></div></div>

<h2 id="adding-conda">Adding Conda</h2>

<p><a href="http://conda.io">Conda</a> is a great cross-platform package manager that has a lot of traction.
You can incorporate Conda as follows, which I’ll describe using the example <a href="https://github.com/matsen/astronaut-requests">astronaut-requests</a> repository.
This example uses the <a href="https://requests.readthedocs.io">requests</a> Python library to fetch the collection of astronauts currently in space.</p>

<p>We just need one more file, which is the <code class="language-plaintext highlighter-rouge">environment.yml</code> file.
This file <a href="https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#create-env-file-manually">specifies your Conda dependencies</a>.
It’s a helpful file for folks to have in general if they wish to install your software via conda, as they can just execute</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>conda env create -f environment.yml
</code></pre></div></div>

<p>and get a Conda environment ready for them.
In the <a href="https://github.com/matsen/astronaut-requests/blob/master/environment.yml">example environment.yml file</a> I show how to combine pip and conda.</p>

<p>The <a href="https://github.com/matsen/astronaut-requests/blob/master/Dockerfile">astronaut-requests Dockerfile</a> just builds the corresponding Conda environment:</p>

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

<span class="k">COPY</span><span class="s"> environment.yml .</span>

<span class="k">RUN </span>/opt/conda/bin/conda <span class="nb">env </span>create <span class="nt">-f</span> environment.yml
</code></pre></div></div>

<p>Here we use the <code class="language-plaintext highlighter-rouge">continuumio/anaconda3:2019.07</code> base image (using a specific tag is a good idea to avoid future breakage!).
We then copy the <code class="language-plaintext highlighter-rouge">environment.yml</code> file in from the repository, and use it to make our Conda environment (note that we need to use the full path to <code class="language-plaintext highlighter-rouge">conda</code>).
In our case it creates a Conda environment called <code class="language-plaintext highlighter-rouge">requests</code>.</p>

<p>The <a href="https://github.com/matsen/astronaut-requests/blob/master/.travis.yml">astronaut-requests Travis YAML file</a> just gets a tiny bit more complex.
I’ll break it down:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">docker run -t quay.io/matsen/astronaut-requests</code> runs commands in the <code class="language-plaintext highlighter-rouge">astronaut-requests</code> container hosted on quay.io as before</li>
  <li><code class="language-plaintext highlighter-rouge">-v ${PWD}:/astronauts</code> mounts the current directory (containing the clone of the repository on Travis) as <code class="language-plaintext highlighter-rouge">/astronauts</code> inside the container</li>
  <li><code class="language-plaintext highlighter-rouge">/bin/bash -c "..."</code> runs <code class="language-plaintext highlighter-rouge">...</code> as a bash command inside the container</li>
  <li><code class="language-plaintext highlighter-rouge">/opt/conda/bin/conda run -n requests ...</code> runs <code class="language-plaintext highlighter-rouge">...</code> inside the <code class="language-plaintext highlighter-rouge">requests</code> Conda environment that we built in our Docker container</li>
  <li><code class="language-plaintext highlighter-rouge">python /astronauts/get-astronauts.py</code> is the command we are running</li>
</ul>

<p>If we look at the <a href="https://travis-ci.org/matsen/astronaut-requests">Travis page for the repository</a> we can see a JSON blob at the bottom with the current astronauts in space at the bottom.
Success!</p>

<p>Note: <code class="language-plaintext highlighter-rouge">conda run</code> is not documented as of right now on the online Conda documentation, but it’s lovely and is documented via <code class="language-plaintext highlighter-rouge">conda run --help</code> on your local installation.</p>

<h2 id="wrap">Wrap</h2>

<p>That’s it!
In practice, you’ll want Travis to build and test your code.
You can see an example of all of this working in our <a href="https://github.com/phylovi/libsbn">libsbn</a> repository.</p>

<p>I emphasize that we used Docker as a build environment and that the code is brought in from the outside.
If you want to build a Docker container with the code built in, you can <a href="https://docs.travis-ci.com/user/docker/#pushing-a-docker-image-to-a-registry">push the resulting Docker image to a registry</a>.</p>]]></content><author><name>Erick Matsen</name><email>matsen@fredhutch.org</email></author><summary type="html"><![CDATA[Docker can make testing your project using Travis very simple (including if you use Conda).]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://erick.matsen.org/github-travis-docker-conda.png" /><media:content medium="image" url="http://erick.matsen.org/github-travis-docker-conda.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Cycling South Korea and Tsushima, Japan</title><link href="http://erick.matsen.org/2019/05/26/four-rivers-korea.html" rel="alternate" type="text/html" title="Cycling South Korea and Tsushima, Japan" /><published>2019-05-26T00:00:00+00:00</published><updated>2019-05-26T00:00:00+00:00</updated><id>http://erick.matsen.org/2019/05/26/four-rivers-korea</id><content type="html" xml:base="http://erick.matsen.org/2019/05/26/four-rivers-korea.html"><![CDATA[<p><img src="/public/images/korea-boardwalk.jpg" alt="" /></p>

<h2 id="overview">Overview</h2>
<p>We had a delightful time riding from Seoul to Busan along the Four Rivers trail.
We then took a ferry to Tsushima for another week of riding, which was delicious icing on the cake.
If you want to go, here are some tips that might be useful.</p>

<h2 id="season">Season</h2>
<p>Air pollution in Korea is <a href="https://www.npr.org/sections/parallels/2016/06/03/478796463/koreas-air-is-dirty-but-its-not-all-close-neighbor-chinas-fault">not a joke</a>.
The locals say that it’s primarily a problem in the spring (and yes, they do blame it on China).
Doing some web searching I couldn’t find compelling evidence that there’s a strong seasonality, but it’s worth some research.
Looking retrospectively at the pollution levels for <a href="https://air.plumelabs.com/en/year/seoul">Seoul</a> and <a href="https://air.plumelabs.com/en/year/busan">Busan</a> we happened to get lucky with the smog levels for our April 2019 trip.
The Koreans, including cyclists, frequently wear masks, and you might consider doing the same.</p>

<p>We had perfect weather: a little cold at the beginning and a little hot at the end.
It seemed like we had a predominant tailwind going North to South.
We had two afternoons of rain, and that was all.</p>

<h2 id="maps">Maps</h2>
<p>We found it very useful to have offline GPS maps with the route marked on the map.
Although the route is very well signed, there are inevitably times when you’ve veered off course and need to find your way back.
I was quite happy with the <a href="https://gurumaps.app/">Guru Maps</a> app available for iOS and Android.
<a href="https://www.google.com/maps/d/u/0/viewer?mid=1c4fmoNXFOXrF_ununh61yaKbOPo&amp;hl=en&amp;ll=37.1885848030395%2C127.43622289213869&amp;z=10">Here</a> are a nice set of waypoints for the trip that you can upload into Guru Maps by exporting them as a KML file.
Note that you will deviate somewhat from these waypoints, say by going on the other side of the river for a stretch, but it’s still a useful handrail.</p>

<p>The OpenStreetMap map available through the app was remarkably accurate.</p>

<h2 id="travel">Travel</h2>
<p>We took our bikes on Asiana Airlines, which flew them as baggage without extra charges.
The bikes were partially disassembled, and then packed into cardboard boxes we got from a bike shop.
(Protip: get the biggest boxes you can, and don’t take off the rear wheel. This will save you from having to take off the derailleur, fenders, etc.)
We also brought along a trailer for our daughter, which has accessory wheels that allows it to pass as a stroller.
This allowed us to gate-check the trailer, which was handy in lots of ways.
Although it’s possible to rent bikes and panniers in Seoul, we were happy to have our own kit.</p>

<p>Taking your bikes around the country on the long-distance buses was straightforward.
They have large cargo bays going the width of the buses that fit all of our gear easily.
I would even venture to say that they would fit a tandem, though I didn’t take measurements.
However, tandems would be hard in general: we rode dozens of smallish elevators with our bikes getting in and out of the airport, as well as up and down overpasses.</p>

<h2 id="arrival">Arrival</h2>
<p>We booked nights on either end of our trip at the <a href="http://www.airporthotel.co.kr/en/">Best Western Incheon Airport</a>, which was willing to store our bike boxes for a month.
They also have a regular shuttle to and from the airport that has cargo space big enough to take bikes in boxes.
The back entrance to the hotel has a nice cement area for assembling bikes.</p>

<p>From the Best Western, you can bike back to the airport (along the maglev train line) in order to take the train into Seoul.
The trip from the airport to Seoul is over water and not bikeable.
We took the all-stop train to Cheonga International City station, from which you can ride back to the Ara West Lock start of the bike trail.
You get on the bike trail <em>behind</em> the Cheonga International City station stop (don’t cross the parking lot).
You can get your stamp book from the information center in that complex.</p>

<h2 id="seoul">Seoul</h2>
<p>Seoul was one of our favorite parts of the trip.
We booked two nights ahead of time in the modest <a href="http://monaihouse.com">Monai guest house</a> which is close to the trail and a subway stop to get around the city.
The owner Joseph was very kind and allowed us to store our bikes inside during our stay (warn him ahead of time that you’re bringing bikes).
He has fantastic English.</p>

<p>You will find your own delights, but we wouldn’t have wanted to miss going to the Changdeokgung palace and its secret garden.
I’d say that the zillion-dish vegan meal we had at <a href="http://lindagoeseast.com/2017/10/17/sanchon-seouls-famous-vegan-temple-food-restaurant/">Sanchon</a> near the palace was one of the 5 most memorable meals of my life (though it wasn’t exactly a budget meal).</p>

<h2 id="riding">Riding</h2>
<p>The riding is mostly on well-paved bike-only trails.
It’s also mostly flat, except for when it’s not: there are two biggish climbs and 100 or so shorter inclines when you leave the river.
Some of these are quite steep, with grades of 15% and more, so bring your low gears.</p>

<h2 id="camping">Camping</h2>
<p>We mostly camped.
We enjoyed the campgrounds when they were available, and when they weren’t we could always find a spot down by the river.
Korean fishermen also set up tents by the river making this wild camping somewhat normal.
Nobody hassled us for camping, but we did still try to stay out of sight in the reeds, just in case.
Some campgrounds in Tsushima were closed because April was early in their camping season.</p>

<p>Although we loved eating in restaurants as much as we could, having a little cooking setup increased our flexibility.
Propane canisters are easy to find in the larger grocery stores.
They are called “gas-u” but taking your old one or having a picture of a canister will ease your canister shopping.</p>

<p>We were happy to have our gravity-feed water filter.
We certainly wanted a water bag to be able to transport water, and having the filter allowed us to use some water that was marked as non-potable.
We didn’t hesitate to drink tap water.</p>

<h2 id="phones-and-electronics">Phones and electronics</h2>
<p>Because we were depending on our phones for navigation, I borrowed quite a chunky external battery for the trip so we wouldn’t run out of juice.
That wasn’t really necessary, and a small one for a charge or two would have been sufficient.
Restaurants all seemed to have outlets available and don’t seem to mind you charging, and all of the campgrounds had electricity as well.</p>

<p>Although just offline maps would have been fine if we had stuck to the main trail, cell data allowed us to be more spontaneous and recover from issues.
Specifically, cell data will allow you to use Naver maps, the excellent Korean maps app, to see where the bike routes are, and to navigate places that you learn about en route.
We would have gotten stuck a couple of times without having it, and having the web available to learn more about what we were seeing enriched our experience.</p>

<p>I used Google Fi, and the service was flawless even in the most remote locations of Korea.
The service in Tsushima, Japan, was a little spottier (but this is simply a function of the cell network rather than the service).
Even though I was liberal in my data use in Korea and Japan, my Fi bill was just $5 more than usual.</p>

<h2 id="spend-a-night-in-hahoe">Spend a night in Hahoe</h2>
<p>Staying a night in the UNESCO world heritage Hahoe village was a truly special experience.
We stayed in centuries-old house that used a traditional wood-burning ondol floor heating system.
Staying there will allow you an early morning walk in the quiet mist through Hahoe.</p>

<p><img src="/public/images/hahoe-guesthouse.jpg" alt="" /></p>

<p>Although it’s not directly on the North-South main line, Hahoe is definitely worth a side-trip.
It’s also an easy place to start or end your trip, as there is good bus service there.</p>

<h2 id="ride-it-with-your-family">Ride it with your family</h2>
<p>We took our 7-month-old daughter along for the trip, which made it such a special family experience.
She was our “ambassador,” and we had so many more interactions with locals because of her.
Our friends rode with us to Hahoe with a 3 and a 5 year old in trailers and trail-a-bike, which was truly remarkable.
We were comfortable with the safety level of the trip, given that it was predominantly on bike-only paths, but you will have to make your own choice.</p>

<p>Both Korea and Japan felt perfectly safe in every other way.
For example, we parked our bikes outside a bustling market in Busan (the Korean equivalent of LA) with all of our stuff on them and came back an hour later with everything untouched.
We brought a reasonably beefy cable lock, but would have been fine with a very light-weight one.</p>

<h2 id="tsushima-japan">Tsushima, Japan</h2>
<p>Tsushima was stunning.
The island is a jungly forest, interrupted only by picturesque fishing villages in every cove.
Here’s the view from <a href="https://www.google.com/maps/place/Mount+Eboshi+Observatory/@34.359197,129.3206476,14z/data=!4m5!3m4!1s0x3569761efb81ef63:0x9dfd72a1dffbef4a!8m2!3d34.3733768!4d129.3165761">Mount Eboshi Observatory</a>:</p>

<p><img src="/public/images/eboshi-observatory.jpg" alt="" /></p>

<p>The two home-stays we did here were very special.
The dinners they served us had many interesting dishes, and the raw seafood was particularly notable.
The Japanese use Google Maps, and you can find home-stays by looking for the bed icon.</p>

<p>There are lots of ferries from the International Ferry Terminal in Busan.
Getting to the ferry terminal from the end of the bike trail is not easy.
You can only take your bike on the subway during the weekends, and city buses do not take bikes.
We rode our bikes there, which involved a lot of riding on bumpy sidewalks and a little bit on big roads.
Naver was crucial for that leg.
Getting back, we worked with the super-helpful folks at the tourist information office to arrange a taxi to the bus terminal.</p>

<p>Not all ferry services allow you to take bikes, but DAE-A Express Shipping Co do.</p>

<p>Japan is much more oriented towards cash versus card.
We didn’t have luck with the ATMs at banks, but the post office ATMs worked great.
Get some cash where you can.</p>

<p>There aren’t grocery stores everywhere in Tsushima, so stock up when you have the opportunity.</p>]]></content><author><name>Erick Matsen</name><email>matsen@fredhutch.org</email></author><summary type="html"><![CDATA[Some tips on the Four Rivers trail across South Korea, and the island of Tsushima.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://erick.matsen.org/korea-boardwalk.jpg" /><media:content medium="image" url="http://erick.matsen.org/korea-boardwalk.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Docker for the busy researcher</title><link href="http://erick.matsen.org/2018/04/19/docker.html" rel="alternate" type="text/html" title="Docker for the busy researcher" /><published>2018-04-19T00:00:00+00:00</published><updated>2018-04-19T00:00:00+00:00</updated><id>http://erick.matsen.org/2018/04/19/docker</id><content type="html" xml:base="http://erick.matsen.org/2018/04/19/docker.html"><![CDATA[<h2 id="why-docker">Why Docker?</h2>
<p>Have you ever been frustrated because a software package’s installation instructions were incomplete?
Or have you wanted to try out software without going through a complex installation process?
Or have you wanted to execute your software on some remote machine in a defined environment?</p>

<p>Docker can help.</p>

<p>In my group, we use Docker to make sure that our code compiles properly in a defined environment and analyses are reproducible.
We automatically create Docker images through <a href="https://docs.docker.com/articles/dockerfile_best-practices/">Dockerfiles</a>.
This provides a clear list of dependencies which are guaranteed to work starting from a defined starting point.</p>

<p>Once a Docker image is built, it can be run anywhere that runs the Docker engine.</p>

<h2 id="introduction-to-docker">Introduction to Docker</h2>
<p>Have you ever seen someone boot up Windows on a Mac?
That process of running one complete OS on top of another OS called running a <em>virtual machine</em>.</p>

<p><img src="/public/images/container-vs-vm.svg" alt="" /></p>

<p>Docker containers are like virtual machines except that they share guest OSs, which makes them very lightweight.
However, it’s still useful to think of Docker containers as virtual machines, because they feel like their own self-contained units.</p>

<p>Docker images are analogous to virtual machine images, and a container is analogous to a running virtual machine.
There is a whole ecosystem around Docker containers, somewhat analogous to git repositories.</p>

<p><img src="/public/images/docker-stages.png" alt="" /></p>

<p><a href="https://hub.docker.com/">Docker Hub</a> plays the role of GitHub (shown as a “Docker registry” above).</p>

<h2 id="pulling-and-running-a-docker-image">Pulling and running a Docker image</h2>

<p>Let’s get our feet wet.
To follow along, you’ll need to <a href="https://docs.docker.com/install/">install Docker</a>, or if you are on a shared machine you’ll need to be added to the <code class="language-plaintext highlighter-rouge">docker</code> group.</p>

<p>First, note that I don’t have cowsay available on the machine running Docker:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>» cowsay
zsh: command not found: cowsay
</code></pre></div></div>

<p>We aim to remedy this situation with Docker.
First pull a Docker image:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>» docker pull matsen/cowsay-test
</code></pre></div></div>

<p>Once that is done, start and enter the Docker container:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>» docker run -it matsen/cowsay-test /bin/bash
root@5d2942f9d5e2:/test-cowsay# cowsay "Hello"
 _______
&lt; Hello &gt;
 -------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
</code></pre></div></div>

<p>Note that my prompt on the host machine is <code class="language-plaintext highlighter-rouge">»</code> whereas that inside the container is <code class="language-plaintext highlighter-rouge">root@5d2942f9d5e2:/test-cowsay#</code>.</p>

<p>Explore around a little inside the container and note that you can’t see anything from your host machine.
Also, any modifications you make inside the container don’t change things on the host machine.
For the ultimate test of this, it’s fun to recursively remove everything starting at <code class="language-plaintext highlighter-rouge">/</code>, though of course be very careful that you are actually inside the container!
In any case, when you are done you can <code class="language-plaintext highlighter-rouge">exit</code> out of the Docker image.</p>

<p>If this all seems rather abstract, think of entering your Docker container as being like connecting to a remote computer.
You need to open a connection to the operating system inside the Docker container via the <code class="language-plaintext highlighter-rouge">docker run</code> command, just like you open a connection to your computing cluster via the <code class="language-plaintext highlighter-rouge">ssh</code> command.
In the next section, we’ll see how you need to create a share between the Docker container and your filesystem just like you would for a remote computer over the network.</p>

<h2 id="making-a-directory-available-inside-of-a-container">Making a directory available inside of a container</h2>

<p>As we have seen, Docker is designed such that changes inside the container are not reflected on the host machine.
That’s perfect for some use cases, if you want to keep something, you typically want it stored outside of the container.
For this Docker provides <a href="https://docs.docker.com/storage/volumes/">volumes</a>.</p>

<p>To have a directory available in a Docker container, the syntax is</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-v /host/path:/container/path
</code></pre></div></div>
<p>where <code class="language-plaintext highlighter-rouge">/host/path</code> should be replaced by the directory on the host machine you wish to make available, and <code class="language-plaintext highlighter-rouge">/container/path</code> is where you would like this directory to appear inside of the container.</p>

<p>For example, say we have a <code class="language-plaintext highlighter-rouge">~/foo</code> directory that we would like available inside our container.
We can start a container and mount this directory like so:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>» docker run -v ${HOME}/foo:/foo -it matsen/cowsay-test /bin/bash
</code></pre></div></div>

<p>Once we’ve mounted it we can do stuff that lives beyond our container:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@2cdca42bc8d9:/test-cowsay# cd /foo
root@2cdca42bc8d9:/foo# cowsay "Hello foo" &gt; output.txt
root@2cdca42bc8d9:/foo# exit
exit
» cat ~/foo/output.txt
 ___________
&lt; Hello foo &gt;
 -----------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
</code></pre></div></div>

<h2 id="dockerfiles">Dockerfiles</h2>

<p>Dockerfiles are complete instructions about how to build a Docker image.
This makes them very valuable even for people who don’t want to use your software via Docker: they can read this document, knowing that it provides a complete set of installation instructions (especially if you are using an automated build as described below).</p>

<p>We will illustrate using an example <a href="https://github.com/matsen/cowsay-test">cowsay-test repository</a> that made the image we pulled above.
The first line,</p>

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

<p>states that the image should be built starting with the <a href="https://en.wikipedia.org/wiki/Ubuntu_version_history#Ubuntu_14.04_LTS_(Trusty_Tahr)">Trusty</a> version of the Ubuntu Linux distribution.
This next section installs <code class="language-plaintext highlighter-rouge">cowsay</code> using <code class="language-plaintext highlighter-rouge">apt-get</code> (more on apt below):</p>

<div class="language-docker highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">RUN </span>apt-get update <span class="nt">-q</span> <span class="o">&amp;&amp;</span> <span class="se">\
</span>    apt-get <span class="nb">install</span> <span class="nt">-y</span> <span class="nt">-q</span> <span class="nt">--no-install-recommends</span> <span class="se">\
</span>        cowsay
</code></pre></div></div>

<p>Then we link <code class="language-plaintext highlighter-rouge">cowsay</code> to a location from which we can execute it.</p>

<div class="language-docker highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">RUN </span><span class="nb">ln</span> <span class="nt">-s</span> /usr/games/cowsay /usr/bin
</code></pre></div></div>

<p>These <code class="language-plaintext highlighter-rouge">FROM</code> and <code class="language-plaintext highlighter-rouge">RUN</code> commands are Docker instructions; see the <a href="https://docs.docker.com/engine/reference/builder/#usage">Dockerfile reference</a> for more details.</p>

<h2 id="building-a-docker-image">Building a Docker image</h2>

<p>Next we’ll build a Docker image from a Dockerfile.
To follow along, clone the <a href="https://github.com/matsen/cowsay-test">cowsay-test repository</a> and enter that directory.
To build the Docker image, use the command</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>» docker build -t matsen/cowsay-test .
</code></pre></div></div>

<p>This builds the Docker image we used in the previous section using the Dockerfile in the current directory.</p>

<h2 id="image-names">Image names</h2>

<p>So far we’ve been working with a Docker image named <code class="language-plaintext highlighter-rouge">matsen/cowsay-test</code>.
We used this both when we pulled from Docker Hub, and when we built it locally (using the <code class="language-plaintext highlighter-rouge">-t</code> flag).
This image name is composed of a user name <code class="language-plaintext highlighter-rouge">matsen</code>, and the name <code class="language-plaintext highlighter-rouge">cowsay-test</code>.</p>

<p>Names can also have <em>tags</em>.
For example, <code class="language-plaintext highlighter-rouge">rocker/tidyverse:3.5</code> is tag <code class="language-plaintext highlighter-rouge">3.5</code> of <code class="language-plaintext highlighter-rouge">rocker/tidyverse</code>.
The <code class="language-plaintext highlighter-rouge">latest</code> tag is special: it is the latest version that was built without an explicit tag version (<a href="https://medium.com/@mccode/the-misunderstood-docker-tag-latest-af3babfd6375">which may be different than the latest built</a>).</p>

<h2 id="a-tiny-bit-about-apt">A tiny bit about apt</h2>

<p>We’re going to interrupt our discussion of Docker briefly to describe the wonderful <a href="https://en.wikipedia.org/wiki/APT_(Debian)">apt</a> package manager that was developed by Debian and adopted by Ubuntu (a Debian derivative).
<a href="http://www.tutonics.com/2012/10/a-faq-style-introduction-to-apt-get-and.html">Here</a> is a FAQ with common commands.
This section is only relevant for base images based eventually on Debian or Ubuntu.
If you have to use an image based on Red Hat (e.g. CentOS), then look up info on <a href="https://www.centos.org/docs/4/html/yum/">yum</a> and/or <a href="https://fedoraproject.org/wiki/DNF">DNF</a>.</p>

<p>Here’s a good apt recipe:</p>

<div class="language-docker highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">RUN </span>apt-get update <span class="o">&amp;&amp;</span> apt-get <span class="nb">install</span> <span class="nt">-y</span> <span class="nt">--no-install-recommends</span> <span class="se">\
</span>    package1 <span class="se">\
</span>    package2
</code></pre></div></div>

<p>Replace <code class="language-plaintext highlighter-rouge">package1</code> and <code class="language-plaintext highlighter-rouge">package2</code> with what your image needs.</p>

<p>To find what you need, do the following.</p>

<ol>
  <li>Learn what version of Debian or Ubuntu you are using in your base image.
For example, if the base image is <code class="language-plaintext highlighter-rouge">debian:latest</code> you can go to <a href="https://hub.docker.com/r/_/debian/">the corresponding Docker Hub page</a> to learn that this is Debian <code class="language-plaintext highlighter-rouge">stretch</code>.</li>
  <li>Run your install until it says that it’s missing some package or header file. Say it’s <code class="language-plaintext highlighter-rouge">libpng</code>.</li>
  <li>Do a web search for <code class="language-plaintext highlighter-rouge">debian package libpng</code>, which will take you to a page like <a href="https://packages.debian.org/search?keywords=libpng">this</a>, telling you that there’s a <code class="language-plaintext highlighter-rouge">libpng-dev</code> package. You can click on <code class="language-plaintext highlighter-rouge">stretch</code> to get more details on what’s up with that package in the <code class="language-plaintext highlighter-rouge">stretch</code> version of Debian.</li>
</ol>

<p>Tip: packages that end with <code class="language-plaintext highlighter-rouge">-dev</code> are generally the ones you want, because they install libraries with the corresponding header files that you can use for compilation.</p>

<h2 id="dockerfiles-making-your-own">Dockerfiles: making your own</h2>

<p>Here we’ll extend our cowsay Docker image a little by extending its powers with <code class="language-plaintext highlighter-rouge">wget</code>.
Doing the steps above, we find that there is a <a href="https://packages.debian.org/stretch/web/wget">wget Debian package</a>.
So we can just add it:</p>

<div class="language-docker highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">RUN </span>apt-get update <span class="nt">-q</span> <span class="o">&amp;&amp;</span> <span class="se">\
</span>    apt-get <span class="nb">install</span> <span class="nt">-y</span> <span class="nt">-q</span> <span class="nt">--no-install-recommends</span> <span class="se">\
</span>        cowsay <span class="se">\
</span>        wget
</code></pre></div></div>

<p>Now, building and running our container as above, we win!
(Note that I’ve abbreviated the command line prompt to <code class="language-plaintext highlighter-rouge">#</code> below).</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># wget --no-check-certificate -O docker.cow https://git.io/vpOr5
# cowsay -f /test-cowsay/docker.cow "w00t"
 ______
&lt; w00t &gt;
 ------
    \
     \
      \
                    ##         .
              ## ## ##        ==
           ## ## ## ## ##    ===
       /"""""""""""""""""\___/ ===
      {                       /  ===-
       \______ O           __/
         \    \         __/
          \____\_______/

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

<p>I suggest iteratively building up your Dockerfile as follows:</p>

<ol>
  <li>Start with a Dockerfile that doesn’t have everything you want, but does build.</li>
  <li>Build and enter the corresponding container.</li>
  <li>Run the install process for your tool, until it hits some problem.</li>
  <li>Install the required dependency interactively within the container.</li>
  <li>If that advances your tool install process, add that dependency install to your Dockerfile.</li>
  <li>Rebuild and re-enter your container and continue the process.</li>
</ol>

<h2 id="tips-for-making-your-dockerfile">Tips for making your Dockerfile</h2>

<ul>
  <li>Start with the best base image you can. If your code extends some already Dockerized software tool, start with that as a base image. For R, see <a href="https://hub.docker.com/u/rocker/">rocker</a>. For conda, see <a href="https://hub.docker.com/r/continuumio/miniconda/">miniconda</a>.</li>
  <li>If you are installing dependencies via <code class="language-plaintext highlighter-rouge">pip</code>, <code class="language-plaintext highlighter-rouge">R</code>, or <code class="language-plaintext highlighter-rouge">conda</code>, go crazy with those, and if you have binary dependencies you may want to use apt.</li>
  <li>Keep it clean. Don’t add extra things, because others will be looking at your Dockerfile and using it as a guide for installing your software locally.</li>
  <li>Docker is very clever at caching partial execution, but this can be frustrating if something has been updated remotely. To start from scratch, use the <code class="language-plaintext highlighter-rouge">--no-cache</code> flag to <code class="language-plaintext highlighter-rouge">docker build</code>.</li>
  <li>If you want to “shelve” your Dockerfile and still have it work down the road without modification, make sure to use a tag. So, rather than basing your image on <code class="language-plaintext highlighter-rouge">continuumio/miniconda</code>, base it on <code class="language-plaintext highlighter-rouge">continuumio/miniconda:4.7.10</code>. That way your build won’t break when the base image is updated.</li>
</ul>

<h2 id="a-minimal-list-of-commands">A minimal list of commands</h2>

<ul>
  <li><code class="language-plaintext highlighter-rouge">docker build .</code>: build a Docker image from this directory (assuming it contains a Dockerfile)</li>
  <li><code class="language-plaintext highlighter-rouge">docker run &lt;image&gt;</code>: run an image, executing the <code class="language-plaintext highlighter-rouge">CMD</code> line then exiting</li>
  <li><code class="language-plaintext highlighter-rouge">docker run -it &lt;image&gt; /bin/bash</code>: run an image and “log into” it</li>
  <li><code class="language-plaintext highlighter-rouge">docker exec -it &lt;container&gt; /bin/bash</code>: “log into” a running container</li>
  <li><code class="language-plaintext highlighter-rouge">docker ps -a</code>: shows all of the containers and their statuses</li>
</ul>

<h2 id="automated-builds">Automated builds</h2>

<p>Docker Hub can automatically build your Docker image every time you push to a GitHub or Bitbucket repository.
To do so, follow these steps:</p>

<ol>
  <li>Write a Dockerfile for your project. Test it locally.</li>
  <li>Make an account on <a href="https://hub.docker.com/">Docker Hub</a> if you don’t have one already.</li>
  <li>Add a <a href="https://docs.docker.com/docker-hub/builds/">Docker automated build</a>.</li>
  <li>Add a Docker build badge to your README (see <a href="https://github.com/matsen/cowsay-test">cowsay-test</a> again, changing what’s needed in the paths).</li>
</ol>

<h2 id="where-to-next">Where to next?</h2>

<p>Having your tool in a Docker container enables a few more next steps.</p>

<ul>
  <li>You can set up your tool in a <a href="https://en.wikipedia.org/wiki/Continuous_testing">continuous testing</a> framework that uses Docker, such as <a href="http://travis-ci.org/">Travis</a>, <a href="https://circleci.com/">CircleCI</a> or <a href="http://www.wercker.com/">Wercker</a>.</li>
  <li>You can use Amazon’s version of a compute queue, called <a href="https://aws.amazon.com/batch/">AWS Batch</a>, to run your tool on large data sets in the cloud.</li>
  <li>To run a Docker image on your local cluster, you will need to use <a href="https://singularity.lbl.gov/docs-docker">Singularity</a> and convert your Docker image into a Singularity image then run it under Singularity. (Cluster administrators don’t like Docker because it doesn’t have a security model.)</li>
</ul>

<h2 id="resources">Resources</h2>

<p>There is a <em>lot</em> more to Docker than we’ve covered in this post.
Here are some good resources.</p>

<ul>
  <li><a href="https://docs.docker.com/get-started/">Docker basics</a></li>
  <li><a href="https://docs.docker.com/install/">Docker installation</a></li>
  <li><a href="https://docs.docker.com/articles/dockerfile_best-practices/">Dockerfile best practices</a></li>
  <li><a href="https://arxiv.org/abs/1410.0846">An introduction to Docker for reproducible research</a>, by <a href="http://www.carlboettiger.info">Carl Boettiger</a></li>
</ul>

<h2 id="thanks">Thanks</h2>

<p>Thank you to Will DeWitt, Jason Vander Heiden, and William Lees for feedback that improved this post.</p>]]></content><author><name>Erick Matsen</name><email>matsen@fredhutch.org</email></author><summary type="html"><![CDATA[This is the minimum I'd want for a friend to know about Docker.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://erick.matsen.org/docker-cow.png" /><media:content medium="image" url="http://erick.matsen.org/docker-cow.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>