<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Fly.io</title>
    <description>The latest articles on DEV Community by Fly.io (@superfly).</description>
    <link>https://dev.to/superfly</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F29009%2F730c0a82-d07f-4c1b-91de-c0ad4cef0407.png</url>
      <title>DEV Community: Fly.io</title>
      <link>https://dev.to/superfly</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/superfly"/>
    <language>en</language>
    <item>
      <title>Docker without Docker</title>
      <dc:creator>Fly.io</dc:creator>
      <pubDate>Wed, 02 Jun 2021 15:42:48 +0000</pubDate>
      <link>https://dev.to/flyio/docker-without-docker-590i</link>
      <guid>https://dev.to/flyio/docker-without-docker-590i</guid>
      <description>&lt;p&gt;We’re Fly.io. We take container images and run them on our hardware around the world.  It’s pretty neat, and you &lt;a href="https://fly.io/docs/speedrun/" rel="noopener noreferrer"&gt;should check it out&lt;/a&gt;; with an already-working Docker container, you can be up and running on Fly in well under 10 minutes.&lt;/p&gt;

&lt;p&gt;Even though most of our users deliver software to us as Docker containers, we don’t use Docker to run them. Docker is great, but we’re high-density multitenant, and despite strides, Docker’s isolation isn’t strong enough for that. So, instead, we transmogrify container images into &lt;a href="https://fly.io/blog/sandboxing-and-workload-isolation/" rel="noopener noreferrer"&gt;Firecracker micro-VMs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Let's demystify that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s An OCI Image?
&lt;/h2&gt;

&lt;p&gt;They do their best to make it look a lot more complicated, but OCI images — OCI is &lt;a href="https://github.com/opencontainers/image-spec" rel="noopener noreferrer"&gt;the standardized container format used by Docker&lt;/a&gt; — are pretty simple.  An OCI image is just a stack of tarballs.&lt;/p&gt;

&lt;p&gt;Backing up: most people build images from Dockerfiles. A useful way to look at a Dockerfile is as a series of shell commands, each generating a tarball; we call these “layers”. To rehydrate a container from its image, we just start the the first layer and unpack one on top of the next.&lt;/p&gt;

&lt;p&gt;You can write a shell script to pull a Docker container from its registry, and that might clarify. Start with some configuration; by default, we’ll grab the base image for &lt;code&gt;golang&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;1&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;golang&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;registry_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'https://registry-1.docker.io'&lt;/span&gt;
&lt;span class="nv"&gt;auth_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'https://auth.docker.io'&lt;/span&gt;
&lt;span class="nv"&gt;svc_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'registry.docker.io'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need to authenticate to pull public images from a Docker registry – this is boring but relevant to the next section – and that’s easy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;function &lt;/span&gt;auth_token &lt;span class="o"&gt;{&lt;/span&gt; 
  curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;auth_url&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/token?service=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;svc_url&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;scope=repository:library/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;image&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:pull"&lt;/span&gt; | jq &lt;span class="nt"&gt;--raw-output&lt;/span&gt; .token
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That token will allow us to grab the “manifest” for the container, which is a JSON index of the parts of a container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;function &lt;/span&gt;manifest &lt;span class="o"&gt;{&lt;/span&gt; 
  &lt;span class="nv"&gt;token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nv"&gt;image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nv"&gt;digest&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;3&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="nv"&gt;latest&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$token&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Accept: application/vnd.docker.distribution.manifest.list.v2+json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Accept: application/vnd.docker.distribution.manifest.v1+json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Accept: application/vnd.docker.distribution.manifest.v2+json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;registry_url&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/v2/library/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;image&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/manifests/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;digest&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first query we make gives us the “manifest list”, which gives us pointers to images for each supported architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"manifests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"digest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:3fc96f3fc8a5566a07ac45759bad6381397f2f629bd9260ab0994ef0dc3b68ca"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"platform"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"architecture"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"amd64"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"os"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"linux"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pull the &lt;code&gt;digest&lt;/code&gt; out of the matching architecture entry and perform the same fetch again with it as an argument, and we get the manifest: JSON pointers to each of the layer tarballs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nl"&gt;"config"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"digest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:0debfc3e0c9eb23d3fc83219afc614d85f0bc67cf21f2b3c0f21b24641e2bb06"&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"layers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"digest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:004f1eed87df3f75f5e2a1a649fa7edd7f713d1300532fd0909bb39cd48437d7"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s as easy to grab the actual data associated with these entries as you’d hope:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;function &lt;/span&gt;blob &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nv"&gt;token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nv"&gt;image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nv"&gt;digest&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$4&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

  curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$token&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
        &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;registry_url&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/v2/library/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;image&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/blobs/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;digest&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And with those pieces in place, pulling an image is simply:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;function &lt;/span&gt;layers &lt;span class="o"&gt;{&lt;/span&gt; 
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;--raw-output&lt;/span&gt; &lt;span class="s1"&gt;'.layers[].digest'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nv"&gt;token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;auth_token &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$image&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;amd64&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;linux_version &lt;span class="si"&gt;$(&lt;/span&gt;manifest &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$token&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$image&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;))&lt;/span&gt;
&lt;span class="nv"&gt;mf&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;manifest &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$token&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$image&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$amd64&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;i&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;span class="k"&gt;for &lt;/span&gt;L &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;layers &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$mf&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;blob &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$token&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$image&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$L&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"layer_&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;i&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.tgz"&lt;/span&gt;
  &lt;span class="nv"&gt;i&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;i &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="k"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unpack the tarballs in order and you’ve got the filesystem layout the container expects to run in. Pull the “config” JSON and you’ve got the entrypoint to run for the container; you could, I guess, pull and run a Docker container with nothing but a shell script, which I’m probably the &lt;a href="https://github.com/p8952/bocker" rel="noopener noreferrer"&gt;1,000th person to point out&lt;/a&gt;. At any rate &lt;a href="https://gist.github.com/tqbf/10006fae0b81d7c7c93513890ff0cf08" rel="noopener noreferrer"&gt;here’s the whole thing&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffly.io%2Fblog%2F2021-04-08%2Fgwg.png%3F2%2F3%26centered" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Ffly.io%2Fblog%2F2021-04-08%2Fgwg.png%3F2%2F3%26centered" alt="Vitally important system diagram"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You’re likely of one of two mindsets about this: (1) that it’s extremely Unixy and thus excellent, or (2) that it’s extremely Unixy and thus horrifying.&lt;/p&gt;

&lt;p&gt;Unix &lt;code&gt;tar&lt;/code&gt; is problematic. Summing up &lt;a href="https://www.cyphar.com/blog/post/20190121-ociv2-images-i-tar" rel="noopener noreferrer"&gt;Aleksa Sarai&lt;/a&gt;: &lt;code&gt;tar&lt;/code&gt; isn’t well standardized, can be unpredictable, and is bad at random access and incremental updates. Tiny changes to large files between layers pointlessly duplicate those files; the poor job &lt;code&gt;tar&lt;/code&gt; does managing container storage is part of why people burn so much time optimizing container image sizes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Another fun detail is that OCI containers share a security footgun with git repositories: it’s easy to accidentally build a secret into a public container, and then inadvertently hide it with an update in a later image.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We’re of a third mindset regarding OCI images, which is that they are horrifying, and that’s liberating. They work pretty well in practice! Look how far they’ve taken us! Relax and make crappier designs; they’re all you probably need.&lt;/p&gt;

&lt;p&gt;Speaking of which:&lt;/p&gt;

&lt;h2&gt;
  
  
  Multi-Tenant Repositories
&lt;/h2&gt;

&lt;p&gt;Back to Fly.io. Our users need to give us OCI containers, so that we can unpack and run them. There’s standard Docker tooling to do that, and we use it: we host a &lt;a href="https://docs.docker.com/registry/spec/api/" rel="noopener noreferrer"&gt;Docker registry&lt;/a&gt; our users push to.&lt;/p&gt;

&lt;p&gt;Running an instance of the Docker registry is very easy. You can do it right now; &lt;code&gt;docker pull registry &amp;amp;&amp;amp; docker run registry&lt;/code&gt;. But our needs are a little more complicated than the standard Docker registry: we need multi-tenancy, and authorization that wraps around our API. This turns out not to be hard, and we can walk you through it.&lt;/p&gt;

&lt;p&gt;A thing to know off the bat: our users drive Fly.io with a command line utility called &lt;code&gt;flyctl&lt;/code&gt;. &lt;code&gt;flyctl&lt;/code&gt; is a Go program (with &lt;a href="https://github.com/superfly/flyctl" rel="noopener noreferrer"&gt;public source&lt;/a&gt;) that runs on Linux, macOS, and Windows. A nice thing about working in Go in a container environment is that the whole ecosystem is built in the same language, and you can get a lot of stuff working quickly just by importing it. So, for instance, we can drive our Docker repository clientside from &lt;code&gt;flyctl&lt;/code&gt; just by calling into Docker’s clientside library.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you're building your own platform and you have the means, I highly recommend the CLI-first tack we took. It is so choice. &lt;code&gt;flyctl&lt;/code&gt; made it very easy to add new features, like &lt;a href="https://fly.io/docs/reference/postgres/" rel="noopener noreferrer"&gt;databases&lt;/a&gt;,&lt;br&gt;
&lt;a href="https://fly.io/blog/incoming-6pn-private-networks/" rel="noopener noreferrer"&gt;private networks&lt;/a&gt;, &lt;a href="https://fly.io/blog/persistent-storage-and-fast-remote-builds/" rel="noopener noreferrer"&gt;volumes&lt;/a&gt;, and our &lt;a href="https://fly.io/blog/ssh-and-user-mode-ip-wireguard/" rel="noopener noreferrer"&gt;bonkers SSH access system&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;On the serverside, we started out simple: we ran an instance of the standard Docker registry with an authorizing proxy in front of it. &lt;code&gt;flyctl&lt;/code&gt; manages a bearer token and uses the Docker APIs to initiate Docker pushes that pass that token; the token authorizes repositories serverside using calls into our API.&lt;/p&gt;

&lt;p&gt;What we do now isn’t much more complicated than that. Instead of running a vanilla Docker registry, we built a custom repository server. As with the client, we get a Docker registry implementation just by importing Docker’s registry code as a Go dependency.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/tqbf/ebd504a625813e6b8c5913fc28cc9515" rel="noopener noreferrer"&gt;We’ve extracted and simplified some of the Go code we used to build this here&lt;/a&gt;, just in case anyone wants to play around with the same idea. This isn’t our production code (in particular, all the actual authentication is ripped out), but it’s not far from it, and as you can see, there’s not much to it.&lt;/p&gt;

&lt;p&gt;Our custom server isn’t architecturally that different from the vanilla registry/proxy system we had before. We wrap the Docker registry API handlers with authorizer middleware that checks tokens, references, and rewrites repository names. There are some very minor gotchas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Docker is content-addressed, with blobs “named” for their SHA256 hashes, and attempts to reuse blobs shared between different repositories. You need to catch those cross-repository mounts and rewrite them.&lt;/li&gt;
&lt;li&gt;Docker’s registry code generates URLs with  &lt;code&gt;_state&lt;/code&gt; parameters that embed references to repositories; those need to get rewritten too. &lt;code&gt;_state&lt;/code&gt; is HMAC-tagged; our code just shares the HMAC key between the registry and the authorizer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In both cases, the source of truth for who has which repositories and where is the database that backs our API server. Your push carries a bearer token that we resolve to an organization ID, and the name of the repository you’re pushing to, and, well, our design is what you’d probably come up with to make that work. I suppose my point here is that it’s pretty easy to slide into the Docker ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building And Running VMs
&lt;/h2&gt;

&lt;p&gt;The pieces are on the board:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We can accept containers from users&lt;/li&gt;
&lt;li&gt;We can store and manage containers for different organizations.&lt;/li&gt;
&lt;li&gt;We've got a VMM engine, Firecracker, that &lt;a href="https://fly.io/blog/sandboxing-and-workload-isolation" rel="noopener noreferrer"&gt;we've written about already&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What we need to do now is arrange those pieces so that we can run containers as Firecracker VMs.&lt;/p&gt;

&lt;p&gt;As far as we're concerned, a container image is just a stack of tarballs and a blob of configuration (we layer additional configuration in as well). The tarballs expand to a directory tree for the VM to run in, and the configuration tells us what binary in that filesystem to run when the VM starts.&lt;/p&gt;

&lt;p&gt;Meanwhile, what Firecracker wants is a set of block devices that Linux will mount as it boots up.&lt;/p&gt;

&lt;p&gt;There's an easy way on Linux to take a directory tree and turn it into a block device: create a file-backed &lt;a href="https://man7.org/linux/man-pages/man4/loop.4.html" rel="noopener noreferrer"&gt;loop device&lt;/a&gt;, and copy the directory tree into it. And that's how we used to do things. When our orchestrator asked to boot up a VM on one of our servers, we would:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pull the matching container from the registry.&lt;/li&gt;
&lt;li&gt;Create a loop device to store the container's filesystem on.&lt;/li&gt;
&lt;li&gt;Unpack the container (in this case, using Docker's Go libraries) into the mounted loop device.&lt;/li&gt;
&lt;li&gt;Create a second block device and inject our init, kernel, configuration, and other goop into.&lt;/li&gt;
&lt;li&gt;Track down any &lt;a href="https://fly.io/blog/persistent-storage-and-fast-remote-builds/" rel="noopener noreferrer"&gt;persistent volumes&lt;/a&gt; attached to the application, unlock them with LUKS, and collect their unlocked block devices.&lt;/li&gt;
&lt;li&gt;Create a &lt;a href="https://en.wikipedia.org/wiki/TUN/TAP" rel="noopener noreferrer"&gt;TAP device&lt;/a&gt;, configure it for our network, and &lt;a href="https://fly.io/blog/bpf-xdp-packet-filters-and-udp/" rel="noopener noreferrer"&gt;attach BPF code to it&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Hand all this stuff off to Firecracker and tell it to boot.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is all a few thousand lines of Go.&lt;/p&gt;

&lt;p&gt;This system worked, but wasn't especially fast. Part of &lt;a href="https://www.usenix.org/system/files/nsdi20-paper-agache.pdf" rel="noopener noreferrer"&gt;the point of Firecracker&lt;/a&gt; is to boot so quickly that you (or AWS) can host Lambda functions in it and not just long-running programs. A big problem for us was caching; a server in, say, Dallas that's asked to run a VM for a customer is very likely to be asked to run more instances of that server (Fly.io apps scale trivially; if you've got 1 of something running and would be happier with 10 of them, you just run &lt;code&gt;flyctl scale count 10&lt;/code&gt;). We did some caching to try to make this faster, but it was of dubious effectiveness.&lt;/p&gt;

&lt;p&gt;The system we'd been running was, as far as container filesystems are concerned, not a whole lot more sophisticated than the shell script at the top of this post. So Jerome replaced it.&lt;/p&gt;

&lt;p&gt;What we do now is run, on each of our servers, an instance of &lt;a href="https://containerd.io/" rel="noopener noreferrer"&gt;&lt;code&gt;containerd&lt;/code&gt;&lt;/a&gt;. &lt;code&gt;containerd&lt;/code&gt; does a whole bunch of stuff, but we use it as as a cache.&lt;/p&gt;

&lt;p&gt;If you're a Unix person from the 1990s like I am, and you just recently started paying attention to how Linux storage works again, you've probably noticed that &lt;em&gt;a lot has changed&lt;/em&gt;. Sometime over the last 20 years, the block device layer in Linux got interesting. LVM2 can pool raw block devices and create synthetic block devices on top of them. It can treat block device sizes as an abstraction, chopping a 1TB block device into 1,000 5GB synthetic devices (so long as you don't actually use 5GB on all those devices!). And it can create snapshots, preserving the blocks on a device in another synthetic device, and sharing those blocks among related devices with copy-on-write semantics.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;containerd&lt;/code&gt; knows how to drive all this LVM2 stuff, and while I guess it's out of fashion to use the &lt;code&gt;devmapper&lt;/code&gt; backend these days, it works beautifully for our purposes. So now, to get an image, we pull it from the registry into our server-local &lt;code&gt;containerd&lt;/code&gt;, configured to run on an LVM2 thin pool. &lt;code&gt;containerd&lt;/code&gt; manages snapshots for every instance of a VM/container that we run. Its API provides a simple "lease"-based garbage collection scheme; when we boot a VM, we take out a lease on a container snapshot (which synthesizes a new block device based on the image, which containerd unpacks for us); LVM2 COW means multiple containers don't step on each other. When a VM terminates, we surrender the lease, and containerd eventually GCs.&lt;/p&gt;

&lt;p&gt;The first deployment of a VM/container on one of our servers does some lifting, but subsequent deployments are lightning fast (the VM build-and-boot process on a second deployment is faster than the logging that we do). &lt;/p&gt;

&lt;h2&gt;
  
  
  Some Words About Init
&lt;/h2&gt;

&lt;p&gt;Jerome wrote our &lt;code&gt;init&lt;/code&gt; in Rust, and, after being cajoled by Josh Triplett, [we released the code (&lt;a href="https://github.com/superfly/init-snapshot" rel="noopener noreferrer"&gt;https://github.com/superfly/init-snapshot&lt;/a&gt;), which you can go read.&lt;/p&gt;

&lt;p&gt;The filesystem that Firecracker is mounting on the snapshot checkout we create is pretty raw. The first job our &lt;code&gt;init&lt;/code&gt; has is to fill in the blanks to fully populate the root filesystem with the mounts that Linux needs to run normal programs. &lt;/p&gt;

&lt;p&gt;We inject a configuration file into each VM that carries the user, network, and entrypoint information needed to run the image. &lt;code&gt;init&lt;/code&gt; reads that and configures the system. We use our own DNS server for private networking, so &lt;code&gt;init&lt;/code&gt; overrides &lt;code&gt;resolv.conf&lt;/code&gt;. We run a tiny SSH server for user logins over WireGuard; &lt;code&gt;init&lt;/code&gt; spawns and monitors that process. We spawn and monitor the entry point program. That’s it; that’s an init.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting It All Together
&lt;/h2&gt;

&lt;p&gt;So, that's about half the idea behind Fly.io. We run server hardware in racks around the world; those servers are tied together with an orchestration system that plugs into our API. Our CLI, &lt;code&gt;flyctl&lt;/code&gt;, uses Docker's tooling to push OCI images to us. Our orchestration system sends messages to servers to convert those OCI images to VMs. It's all pretty neato, but I hope also kind of easy to get your head wrapped around.&lt;/p&gt;

&lt;p&gt;The other "half" of Fly is our Anycast network, which is a CDN built in Rust that uses BGP4 Anycast routing to direct traffic to the nearest instance of your application. About which: more later.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>go</category>
      <category>bash</category>
      <category>linux</category>
    </item>
    <item>
      <title>Building a Distributed Turn-Based Game System in Elixir</title>
      <dc:creator>Fly.io</dc:creator>
      <pubDate>Wed, 26 May 2021 17:38:25 +0000</pubDate>
      <link>https://dev.to/flyio/building-a-distributed-turn-based-game-system-in-elixir-24p1</link>
      <guid>https://dev.to/flyio/building-a-distributed-turn-based-game-system-in-elixir-24p1</guid>
      <description>&lt;p&gt;One of the best things about building web applications in Elixir is LiveView, the &lt;a href="https://www.phoenixframework.org/"&gt;Phoenix Framework&lt;/a&gt; feature that makes it easy to create live and responsive web pages without all the layers people normally build.&lt;/p&gt;

&lt;p&gt;Many great &lt;a href="https://github.com/phoenixframework/phoenix_live_view"&gt;Phoenix LiveView&lt;/a&gt; examples exist. They often show the ease and power of LiveView but stop at multiple browsers talking to a single web server. I wanted to go further and create a fully clustered, globally distributed, privately networked, secure application. What's more, I wanted to have fun doing it.&lt;/p&gt;

&lt;p&gt;So I set out to see if I could create a fully distributed, clustered, privately networked, global game server system. &lt;strong&gt;Spoiler Alert: I did&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I didn't have to build
&lt;/h2&gt;

&lt;p&gt;What I find remarkable is what I &lt;strong&gt;didn't&lt;/strong&gt; need to build.&lt;/p&gt;

&lt;p&gt;I &lt;strong&gt;didn't&lt;/strong&gt; build a Javascript front end using something like React.js or Vue.js. That is the typical approach. Building a JS front-end means I need JS components, a front-end router, a way to model the state in the browser, a way to transfer player actions to the server and a way to receive state updates from the server.&lt;/p&gt;

&lt;p&gt;On the server, I &lt;strong&gt;didn't&lt;/strong&gt; build an API. Typically that would be REST or GraphQL with a JSON structure for transferring data to and from the front-end.&lt;/p&gt;

&lt;p&gt;I &lt;strong&gt;didn't&lt;/strong&gt; need other external systems like Amazon SQS, Kafka, or even just Redis to pass state between servers. This means the entire system requires less cross-technology knowledge or specialized skills to build and maintain it. I used &lt;code&gt;Phoenix.PubSub&lt;/code&gt; which is built on technology already in Elixir's VM, called the BEAM. I used the Horde library to provide a distributed process registry for finding and interacting with GameServers.&lt;/p&gt;

&lt;p&gt;As for &lt;a href="https://fly.io/docs/reference/privatenetwork"&gt;Fly.io's WireGuard connected private network&lt;/a&gt; between &lt;a href="https://fly.io/docs/reference/regions/"&gt;geographically distant regions&lt;/a&gt; and data centers? I don't even know how I would have done that in AWS, which is why I've always given up on the idea.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I did build
&lt;/h2&gt;

&lt;p&gt;What I built was just a proof of concept, but I'm surprised at how it came together. I ended up with a platform that can host many different types of games, all of which:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can be multi-player&lt;/li&gt;
&lt;li&gt;Offer a &lt;a href="https://en.wikipedia.org/wiki/Jackbox_Games"&gt;Jackbox&lt;/a&gt;-style 4-letter game code system&lt;/li&gt;
&lt;li&gt;Have on-demand game and match creation&lt;/li&gt;
&lt;li&gt;With a fast, response UI&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And, just one little extra detail: the platform supports multiple connected servers operating together in clusters. Elixir for the win!&lt;/p&gt;

&lt;p&gt;I created this as an open source project on Github, so you can check it out yourself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/fly-apps/tictac"&gt;https://github.com/fly-apps/tictac&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Technology
&lt;/h2&gt;

&lt;p&gt;I've worked with enough companies and teams to imagine several different approaches to build a system like this. Those approaches would all require large multi-disciplinary teams like a front-end JS team, a backend team, a DevOps team, and more. In contrast, I set out to do this by myself, in my spare time, and with a whole lot of "life" happening too.&lt;/p&gt;

&lt;p&gt;Here's what I chose to use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://elixir-lang.org/"&gt;Elixir programming language&lt;/a&gt; – A dynamic, functional language for building scalable and maintainable applications.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.phoenixframework.org/"&gt;Phoenix Framework&lt;/a&gt; – Elixir's primary web framework&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/phoenixframework/phoenix_live_view"&gt;Phoenix LiveView&lt;/a&gt; – Rich, real-time user experiences with server-rendered HTML delivered by websockets&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/bitwalker/libcluster"&gt;libcluster&lt;/a&gt; – Automatic cluster formation/healing for Elixir applications.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/derekkraan/horde"&gt;Horde&lt;/a&gt; – Elixir library that provides a distributed and supervised process registry.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://fly.io/"&gt;Fly.io&lt;/a&gt; – Hosting platform that enables private networked connections and multi-region support.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Application Architecture
&lt;/h2&gt;

&lt;p&gt;There are many guides to &lt;a href="https://www.google.com/search?hl=en&amp;amp;q=getting%20started%20with%20phoenix%20liveview"&gt;getting started with LiveView&lt;/a&gt;, I'm not focusing on that here. However, for context, this demonstrates the application architecture when running on a local machine.&lt;/p&gt;

&lt;p&gt;The "ABCD" in the graphic is a running game identified by the 4-letter code "ABCD".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oTcQWh26--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://fly.io/public/images/tictac-single-node-game-state-and-gen-server.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oTcQWh26--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://fly.io/public/images/tictac-single-node-game-state-and-gen-server.png" alt="local system architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's walk it through.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A player uses a web browser to view the game board. The player clicks to make a move.&lt;/li&gt;
&lt;li&gt;The browser click triggers an event in the player's LiveView. There is a bi-directional websocket connection from the browser to LiveView.&lt;/li&gt;
&lt;li&gt;The LiveView process sends a message to the game server for the player's move.&lt;/li&gt;
&lt;li&gt;The GameServer uses &lt;code&gt;Phoenix.PubSub&lt;/code&gt; to publish the updated state of game ABCD.&lt;/li&gt;
&lt;li&gt;The player's LiveView is subscribed to notifications for any updates to game ABCD. The LiveView receives the new game state. This automatically triggers LiveView to re-render the game immediately pushing the UI changes out to the player's browser.&lt;/li&gt;
&lt;li&gt;All connected players see the new state of the board and game.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  We need a game
&lt;/h2&gt;

&lt;p&gt;I needed a simple game to play and model for this game system. I chose Tic-Tac-Toe. Why?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It's simple to understand and play.&lt;/li&gt;
&lt;li&gt;Easy to model.&lt;/li&gt;
&lt;li&gt;Doesn't bog down the project with designing a game.&lt;/li&gt;
&lt;li&gt;Quick to play through and test it being "over".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I want to emphasize that this system can be used to build &lt;strong&gt;many&lt;/strong&gt; turn-based, multi-user games! This simple Tic-Tac-Toe game covers all of the basics we will need. Besides, &lt;a href="https://www.youtube.com/watch?v=xHObMqUdBa8"&gt;Tic-Tac-Toe was even made into a TV Show&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;This is what the game looks like with 2 players.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hnduopNf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://fly.io/public/images/tictac_local_playing.gif%3Fcard" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hnduopNf--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://fly.io/public/images/tictac_local_playing.gif%3Fcard" alt="animated gif demoing game play"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The game system works great locally. Let's get it deployed!&lt;/p&gt;

&lt;h2&gt;
  
  
  Hosting on Fly.io
&lt;/h2&gt;

&lt;p&gt;Following the &lt;a href="https://fly.io/docs/getting-started/elixir/"&gt;Fly.io Getting Started Guide for Elixir&lt;/a&gt;, I created a Dockerfile to generate a release for my application. Check out the repo here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/fly-apps/tictac"&gt;https://github.com/fly-apps/tictac&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The README file outlines both how to run it locally and deploy it globally on Fly.io.&lt;/p&gt;

&lt;p&gt;What is special about hosting it on Fly.io? Fly makes it easy to deploy a server geographically closer to the users I want to reach. When a user goes to my website, they are directed to &lt;strong&gt;my nearest server&lt;/strong&gt;. This means any responsive LiveView updates and interactions will be even faster and smoother because the regular TCP and websocket connections are just that much physically closer.&lt;/p&gt;

&lt;p&gt;But for the game, I wanted there to be a single source of truth. That GameServer can only exist in one place. Supporting a private, networked, and fully clustered environment means my server in the EU can communicate with the GameServer that might be running in the US. But my EU players have a fast and responsive UI connection close to them. This provides a better user experience!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dYMKyghO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://fly.io/public/images/tictac-fly-region-cluster.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dYMKyghO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://fly.io/public/images/tictac-fly-region-cluster.png" alt="Fly region clustering"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is what I find compelling about Fly.io for hosting Elixir applications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Secure HTTPS automatically using Let's Encrypt. I didn't do anything to set that up!&lt;/li&gt;
&lt;li&gt;Distributed nodes use &lt;a href="https://fly.io/docs/reference/privatenetwork/"&gt;private network&lt;/a&gt; connections through &lt;a href="https://www.wireguard.com/"&gt;WireGuard&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Nodes auto-clustered using &lt;code&gt;libcluster&lt;/code&gt; and the &lt;code&gt;DNSPoll&lt;/code&gt; strategy. (See &lt;a href="https://github.com/fly-apps/tictac/blob/main/config/runtime.exs#L25"&gt;here for details&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Geographically distributed servers near my users are clustered together.&lt;/li&gt;
&lt;li&gt;This was the easiest multi-region yet still privately networked solution I've ever seen! (I have experience with AWS, DigitalOcean, and Heroku)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;For a proof-of-concept, I couldn't be happier! In a short time, by myself, I created a working, clustered, distributed, multi-player, globe-spanning gaming system!&lt;/p&gt;

&lt;p&gt;The pairing of Elixir + LiveView + Fly.io is excellent. Using Elixir and LiveView, I built a powerful, resilient, and distributed system in orders of magnitude shorter time and effort. Deploying it on Fly.io let let me easily do something I would never have even tried before, namely, deploying servers in regions around the globe while keeping the application privately networked and clustered together.&lt;/p&gt;

&lt;p&gt;Whenever I've thought of creating a service with a global audience, I'd usually scapegoat the idea saying, "Well I don't know how I'd get the translations, so I'll just stick with the US. It's a huge market anyway." In short, I've never even considered a globally connected application because it would be "way too hard".&lt;/p&gt;

&lt;p&gt;But here, with Elixir + LiveView + Fly.io, I did something by myself in my spare time that larger teams using more technologies struggle to deliver. I'm still mind blown by it!&lt;/p&gt;

&lt;h2&gt;
  
  
  What will you build?
&lt;/h2&gt;

&lt;p&gt;Tic-Tac-Toe is a simple game and doesn't provide "hours of fun". I know &lt;strong&gt;you&lt;/strong&gt; can think of a much cooler and more interesting multi-player, turn-based game that you could build on a system like this. What do you have in mind?&lt;/p&gt;

</description>
    </item>
    <item>
      <title>You should know about Server-Side Request Forgery</title>
      <dc:creator>Fly.io</dc:creator>
      <pubDate>Mon, 25 Jan 2021 18:09:17 +0000</pubDate>
      <link>https://dev.to/flyio/practical-smokescreen-sanitizing-your-outbound-web-requests-2519</link>
      <guid>https://dev.to/flyio/practical-smokescreen-sanitizing-your-outbound-web-requests-2519</guid>
      <description>&lt;p&gt;This is a post about the most dangerous vulnerability most web applications face, one step that we took at Fly to mitigate it, and how you can do the same.&lt;/p&gt;




&lt;p&gt;Server-side request forgery (SSRF) is application security jargon for “attackers can get your app server to make HTTP requests on their behalf”. Compared to other high severity vulnerabilities like SQL injection, which allows attackers to take over your database, or filesystem access or remote code injection, SSRF doesn’t sound that scary. But it is, and you should be nervous about it.&lt;/p&gt;

&lt;p&gt;The deceptive severity of SSRF is one of two factors that makes SSRF so insidious. The reason is simple: your app server is behind a security perimeter, and can usually reach things an ordinary Internet user can’t. Because HTTP is a relatively flexible protocol, and URLs are so expressive, attackers can often use SSRF to reach surprising places; in fact, leveraging HTTP SSRF to reach non-SSRF protocols has become a sport among security researchers. A meaty, complicated example of this is &lt;a href="https://portswigger.net/daily-swig/when-tls-hacks-you-security-friend-becomes-a-foe"&gt;Joshua Maddux’s TLS SSRF trick&lt;/a&gt; from last August. Long story short: in serious applications, SSRF is usually a game-over vulnerability, meaning attackers can use it to gain full control over an application’s hosting environment.&lt;/p&gt;

&lt;p&gt;The other factor that makes SSRF nerve-wracking is its prevalence. As an industry, we’ve managed to drastically reduce instances of vulnerabilities like SQL injection by updating our libraries and changing best practices; for instance, it would be weird to see a mainstream SQL library that didn’t use parameterized queries to keep attacker meta-characters out of query parsing. But applications of all shapes and sizes make server-side HTTP queries; in fact, if anything, that’s becoming more common as we adopt more and more web APIs. &lt;/p&gt;

&lt;p&gt;There are two common patterns of SSRF vulnerabilities. The first, simplest, and most dangerous comprises features that allow users to provide URLs for the web server to call directly; for instance, your app might offer “web hooks” to call back to customer web servers. The second pattern involves features that incorporate user data into URLs. In both cases, an attacker will try to leverage whatever control you offer over URLs to trick your server into hitting unexpected URLs.&lt;/p&gt;

&lt;p&gt;Fortunately, there’s a mitigation that frustrates attackers trying to exploit either pattern of SSRF vulnerabilities: SSRF proxies.&lt;/p&gt;

&lt;h2&gt;
  
  
  You should know about Smokescreen
&lt;/h2&gt;

&lt;p&gt;Imagine if your application code didn’t have to be relentlessly vigilant about every URL it reached out to, and could instead assume that a basic security control existed to make sure that no server-side HTTP query would be able to touch internal resources? It’s easy if you try! What you want is to run your server-side HTTP through a proxy.&lt;/p&gt;

&lt;p&gt;We've been putting &lt;a href="https://github.com/stripe/smokescreen"&gt;Smokescreen&lt;/a&gt; to work at Fly, and it's so useful, we thought we should share. Smokescreen is an egress proxy that was built at Stripe to help manage outgoing connections in a sensible and safe way.&lt;/p&gt;

&lt;p&gt;Smokescreen’s job is to make sure your outgoing requests are sane, sensible and safe. SmokeScreen was created by Stripe to ensure that they knew where all their outgoing requests were going. Specifically, it makes sure that the IP address requested is a publicly routed IP and that means checking any request isn't destined for &lt;code&gt;10.0.0.0/8&lt;/code&gt;, &lt;code&gt;172.16.0.0/12&lt;/code&gt;, &lt;code&gt;192.168.0.0/16&lt;/code&gt;, or &lt;code&gt;fc00::/7&lt;/code&gt; (the IPv6 "ULA" space, which includes &lt;a href="https://fly.io/blog/incoming-6pn-private-networks/"&gt;Fly's 6PN private addresses&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Out of the box
&lt;/h2&gt;

&lt;p&gt;There's more SmokeScreen can do, but before we get to that, let's talk about how Smokescreen determines who you are. By default, Smokescreen uses the client certificate from a TLS connection, extracts the common name of the certificate and uses that as the role. There is another mechanism documented for non-TLS connections using a header but doesn't seem to be actually wired up Smokescreen (probably because it's way too simple to present to be another system). So you'll have to use TLS CA certs for all the systems connecting through Smokescreen and that is an administrative pain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Basic
&lt;/h2&gt;

&lt;p&gt;We wanted Smokescreen to be simpler to enable, and with Fly we have the advantage of supporting Secrets for all applications. Rather than repurposing TLS CAs to provide a name, we can store a secret with the Smokescreen proxy and with the app that sends requests to the outside world. That secret? For the example, we've gone with a &lt;code&gt;PROXY_PASSWORD&lt;/code&gt; that we can distribute to all inside the Fly network.&lt;/p&gt;

&lt;p&gt;Here's the &lt;a href="https://github.com/fly-examples/smokescreen"&gt;Fly Github repository for the Fly Smokescreen&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  I'm on the list...
&lt;/h2&gt;

&lt;p&gt;In all cases, what Smokescreen does is turn the identity of an incoming request into a role. That role is then looked up in the &lt;code&gt;acl.yaml&lt;/code&gt; file. Here's the Fly example ACL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;authed&lt;/span&gt;
    &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;users&lt;/span&gt;
    &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;report&lt;/span&gt;


&lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;other&lt;/span&gt;
    &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;enforce&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We've gone super simple on the roles here. There's one and that's &lt;code&gt;authed&lt;/code&gt;. You're either &lt;code&gt;authed&lt;/code&gt; or you fall through to default. The project field is there to make logging more meaningful by associating roles with projects. &lt;/p&gt;

&lt;p&gt;The control of what happens with requests comes from the &lt;code&gt;action&lt;/code&gt; field; this has three settings: &lt;code&gt;open&lt;/code&gt; lets all traffic through, &lt;code&gt;report&lt;/code&gt; lets all traffic through but logs the request if it's not on the list, and &lt;code&gt;enforce&lt;/code&gt; only lets through traffic on the list. The list in this example isn't there, so &lt;code&gt;report&lt;/code&gt; logs all requests and &lt;code&gt;enforce&lt;/code&gt; blocks all requests. &lt;/p&gt;

&lt;p&gt;Adding &lt;code&gt;allowed-domains&lt;/code&gt; and a list of domains lets you fine tune these options. For a general purpose block-or-log egress proxy, this example is enough. Smokescreen has more &lt;a href="https://github.com/stripe/smokescreen#acls"&gt;ACL control options&lt;/a&gt;, including global allow and deny lists if you want to maintain simple but specfic rules but want to block a long list of sites.&lt;/p&gt;

&lt;h2&gt;
  
  
  Smokescreen inside
&lt;/h2&gt;

&lt;p&gt;If you are interested in how this modified Smokescreen works, look in the &lt;a href="https://github.com/fly-examples/smokescreen/blob/master/main.go"&gt;main.go&lt;/a&gt; file. This is where the smokescreen code is loaded as a Go package. The program creates a new configuration for Smokescreen with a alternative &lt;code&gt;RoleFromRequest&lt;/code&gt; function. It's this function that extracts the &lt;code&gt;Proxy-Authorization&lt;/code&gt; password and checks it against the &lt;code&gt;PROXY_PASSWORD&lt;/code&gt; environment variable. If it passes that test, it returns &lt;code&gt;authed&lt;/code&gt; as a role. Otherwise, it returns an empty string, denoting no role. It's this function that you may want to customize to create your own mappings from username and password combinations to Smokescreen roles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy now
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Fly
&lt;/h3&gt;

&lt;p&gt;This is where we show how to deploy on Fly first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fly init mysmokescreen --import source.fly.toml --org personal
fly set secret PROXY_PASSWORD="somesecret"
fly deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it for Fly; there'll be a mysmokescreen app set up with Fly's internal private networking DNS (and external DNS if we needed that, which we don't here), and it'll be up and running. Turn on your &lt;a href="https://fly.io/docs/reference/privatenetwork/#private-network-vpn"&gt;Fly 6PN (Private Networking) VPN&lt;/a&gt; and test it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -U anyname:somesecret -x mysmokescreen.internal:4750 https://fly.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that will return the Fly homepage to you. Run &lt;code&gt;fly logs&lt;/code&gt; and you'll see entries for the opening and closing of the proxy's connection to fly.io. What's neat with the Fly deployment is that with just two commands you can deploy the same application globally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker - locally
&lt;/h3&gt;

&lt;p&gt;If you're on another platform, you should be able to reuse the Dockerfile. Running locally, you just need to do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker build -t smokescreen .
docker run -p 4750:4750 --env PROXY_PASSWORD=somesecret smokescreen
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And to test, in another session, do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -U anyname:somesecret -x localhost:4750 https://fly.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see the log output appearing in the session where you did the &lt;code&gt;docker run&lt;/code&gt;. We leave it as an exercise to readers to deploy the application to their own Cloud.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using a Proxy from an app
&lt;/h2&gt;

&lt;p&gt;To wrap up this article, we present two code examples, one in Go and one in Node, that take from the environment a PROXY_URL pointed at our smokescreen and a PROXY_PASSWORD for that smokescreen and issue a simple GET for an https: URL. &lt;/p&gt;

&lt;p&gt;On Fly, the PROXY_URL can be as simple as "&lt;a href="http://mysmokescreen.internal:4750/"&gt;http://mysmokescreen.internal:4750/&lt;/a&gt;". Fly's 6PN network automatically maps deployed applications' names and instances into the the .internal TLD for DNS. On other platforms, you'll have to configure a hostname for your smokescreen and make sure you change it everywhere if you move your proxy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Calling through an authenticated Proxy from Go
&lt;/h3&gt;

&lt;p&gt;This example uses only the system libraries. There are no extra modules needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"encoding/base64"&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"io/ioutil"&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;
    &lt;span class="s"&gt;"net/url"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Set the proxy's URL&lt;/span&gt;
    &lt;span class="n"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LookupEnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PROXY_URL"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Set PROXY_URL environment variable"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// And parse it.&lt;/span&gt;
    &lt;span class="n"&gt;proxyURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"The proxyURL is unparsable: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;proxyPASS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LookupEnv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"PROXY_PASSWORD"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Set PROXY_PASSWORD environment variable"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Get you a transport that understands Proxies and Proxy authentication&lt;/span&gt;
    &lt;span class="n"&gt;transport&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transport&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Proxy&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProxyURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proxyURL&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;

    &lt;span class="c"&gt;// Create a usename:password string&lt;/span&gt;
    &lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"anyname:"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;proxyPASS&lt;/span&gt;

    &lt;span class="c"&gt;// Base64 that string with "Basic " prepended to it&lt;/span&gt;
    &lt;span class="n"&gt;basicAuth&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"Basic "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StdEncoding&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EncodeToString&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="c"&gt;// Put a header into the proxy connect header&lt;/span&gt;
    &lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProxyConnectHeader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="c"&gt;// And then add in the Proxy-Authorization header with our auth string&lt;/span&gt;
    &lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProxyConnectHeader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Proxy-Authorization"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;basicAuth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Now we are ready to get pages, just create HTTP clients which use the&lt;/span&gt;
    &lt;span class="c"&gt;// Proxy transport.&lt;/span&gt;

    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Transport&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;rawURL&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"https://fly.io"&lt;/span&gt;

    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rawURL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Read ok"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;bs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ioutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bs&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Calling through an authenticated Proxy from Node.js
&lt;/h3&gt;

&lt;p&gt;This example uses the &lt;a href="https://www.npmjs.com/package/https-proxy-agent"&gt;https-proxy-agent&lt;/a&gt; package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;https&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;HttpsProxyAgent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https-proxy-agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Create a URL for our proxy from the env var&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;proxyOpts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PROXY_URL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Get a password from the environment too&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;proxyPass&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PROXY_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Inject a Proxy-Authentication header into the proxy using the password&lt;/span&gt;
&lt;span class="nx"&gt;proxyOpts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Proxy-Authentication&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Basic &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`anyname:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;proxyPass&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Create an HTTPS Proxy Agent with our accumulated options&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;HttpsProxyAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;proxyOpts&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://fly.io&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;"response" event!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Smokescreen summarized
&lt;/h2&gt;

&lt;p&gt;We've shown you examples of setting up a custom Smokescreen with password authentication. You'll find all the code for setting that up at the &lt;a href="https://github.com/fly-examples/smokescreen"&gt;Fly Github repository for this Smokescreen&lt;/a&gt;. Have fun sanitizing your outgoing web requests.&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>security</category>
      <category>ssrf</category>
    </item>
    <item>
      <title>Certificates for your Cloud backend</title>
      <dc:creator>Fly.io</dc:creator>
      <pubDate>Tue, 24 Nov 2020 17:59:02 +0000</pubDate>
      <link>https://dev.to/flyio/certificates-for-your-cloud-backend-50ga</link>
      <guid>https://dev.to/flyio/certificates-for-your-cloud-backend-50ga</guid>
      <description>&lt;p&gt;When you're building a cloud application, the last thing you want is to have to stop while you get yourself some certificates to secure your servers. It costs time and money and wouldn't you rather just print your own certificates for your own use? I mean, you trust you don't you? Assuming you do, then you can likely make use of &lt;em&gt;&lt;a href="https://github.com/FiloSottile/mkcert"&gt;mkcert&lt;/a&gt;&lt;/em&gt; when building out that cloud app. &lt;/p&gt;

&lt;p&gt;If you know &lt;em&gt;mkcert&lt;/em&gt;, you may not think of it as something for a cloud application's backend. &lt;em&gt;Mkcert&lt;/em&gt; was originally developed with another problem in mind, local development. When putting together a web application to run in development on a local machine, it is literally impossible to get a certificate for it from a certificate authority - everyone has a &lt;code&gt;localhost&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;You can try and make your own self-signed certificates to get around that problem. That turns into a bit of a red flag carnival when you use them, with applications complaining about not being able to trust the certificates you've signed as they can't be checked with anyone. As the developers of &lt;em&gt;mkcert&lt;/em&gt; say, "Managing your own CA is the best solution, but usually involves arcane commands, specialized knowledge, and manual steps." &lt;/p&gt;

&lt;h2&gt;
  
  
  Why &lt;em&gt;mkcert&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;That's why they came up with &lt;em&gt;mkcert&lt;/em&gt; which does all that arcane stuff for you, from making a root CA certificate to generating locally trusted certificates. And more importantly, it does it without the strange world of OpenSSL commands and error messages which can quite literally reduce people to tears.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Mkcert&lt;/em&gt; can get it all up and running and it'll talk to your local trust store and make your generated certificates trustable. And there are no weird commands or errors. It's great, but what's this got to do with certificates in the cloud. &lt;/p&gt;

&lt;p&gt;Well, what &lt;em&gt;mkcert&lt;/em&gt; does is also enough to enable TLS for many applications and servers, so you can turn to &lt;em&gt;mkcert&lt;/em&gt; for some fast certificate provisioning for your cloud apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Applying mkcert
&lt;/h2&gt;

&lt;p&gt;Let's show you how it works. Recently at Fly, we put together &lt;a href="https://fly.io/docs/app-guides/redistls/"&gt;an example of a Redis installation with TLS enabled&lt;/a&gt;. To make it easy to reproduce and secure, we used &lt;em&gt;mkcert&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The first step is to install &lt;em&gt;mkcert&lt;/em&gt;. You'll find the instructions for that in it's &lt;a href="https://github.com/FiloSottile/mkcert/blob/master/README.md"&gt;README&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Once it's installed you run &lt;code&gt;mkcert -install&lt;/code&gt; and that's where the magic happens. It creates a functioning local certificate authority (CA) with no intervention and installs that CA locally and, if Firefox is around, into Firefox. The only part of that we're interested in is the fact that it has created a CA because it's from that CA that we can extract the CA root file. This is the certificate that other applications can use to verify any certificates that this &lt;em&gt;mkcert&lt;/em&gt; installation will generate. &lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;mkcert -CAROOT&lt;/code&gt; to get the file name for the CA root file. Copy it somewhere - or combine it all into one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir -p certs
cp "$(mkcert -CAROOT)/rootCA.pem" certs/rootCA.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You now have what is essentially a master key tester for your certificates. We'll be copying this file around our application's infrastructure and... will you stop panicking at the back there. First of all, this is a certificate that's been signed using another key so you can't make new certificates with it, only verify them.&lt;/p&gt;

&lt;p&gt;Secondly, your application's infrastructure should be working like a sealed box, with only specific and auditable entry paths. Beyond common base images, it should be composed of assets and container images that you control access to. &lt;/p&gt;

&lt;p&gt;Copying the CA Root file around inside a closed environment should not be an issue, and if it is, well, you have bigger problems to be concerned with. If you have external access to the system it should be secured by real, verifiable certificates anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cutting Server Keys
&lt;/h2&gt;

&lt;p&gt;Anyway, now we have a CA Root file we can use to verify keys, we can get around to cutting some keys. &lt;/p&gt;

&lt;p&gt;The first key we need in our Redis TLS example is one for the server, which has a hostname of &lt;code&gt;appkata-redistls.fly.dev&lt;/code&gt;. &lt;em&gt;Mkcert&lt;/em&gt; will generate a key and certificate for the server&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkcert -key-file certs/redis-server.key -cert-file certs/redis-server.crt appkata-redistls.fly.dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the certificates done, all we need to do now is configure Redis with those certificates. The certs directory we created should be copied into &lt;code&gt;/etc/&lt;/code&gt; before this is done (as part of the image building) and with that in place we add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tls-port 7379
tls-cert-file /etc/certs/redis-server.crt
tls-key-file /etc/certs/redis-server.key
tls-ca-cert-file /etc/certs/rootCA.pem
tls-auth-clients no
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To the &lt;code&gt;redis.conf&lt;/code&gt; file. That's very Redis specific, but in more generic terms, you pass the cert, key, and ca-cert file to the application's TLS configuration; either through a config file or through command-line flags.&lt;/p&gt;

&lt;p&gt;When the server is brought online, it'll use those certificates and that CA root as a basis for validating the certificate. Of course, as no connecting client will know how to verify the locally generated certificates, you'll have to give the client a copy of the &lt;code&gt;rootCA.pem&lt;/code&gt; file so it can do that for itself. Something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;redis-cli -h appkata-redistls.fly.dev \
           --tls \
          --cacert certs/rootCA.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An incoming client, equipped with that file, will be able to connect over TLS and authenticate with a password if you've set one. But all this does is encrypt the connection.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keys for Clients
&lt;/h2&gt;

&lt;p&gt;We can tighten up security further if we ask the server to demand that anyone connecting has a valid client certificate. The first step in that is turning that requirement on. Over to the &lt;code&gt;redis.conf&lt;/code&gt; file and let's flip the &lt;code&gt;tls-auth-clients&lt;/code&gt; line to read:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;tls-auth-clients yes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can't connect to the server - with Redis, you can connect but you'll be disconnected when you try any command. What we need now are some client certificates, ones that say "This client is allowed to access this named server". Again &lt;em&gt;mkcert&lt;/em&gt; makes it simple. Just add &lt;code&gt;--client&lt;/code&gt; to the &lt;code&gt;mkcert&lt;/code&gt; and it will make client certificates.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkcert --client -key-file redis-client.key -cert-file redis-client.crt appkata-redistls.fly.dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, you can go to a remote system, with these files, &lt;code&gt;redis-client.key&lt;/code&gt;, &lt;code&gt;redis-client.crt&lt;/code&gt; and the &lt;code&gt;rootCA.crt&lt;/code&gt; file and use them to connect as application such as &lt;code&gt;redis-cli&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;redis-cli -h redis-example.fly.dev \
          --cert redis-client.crt \
          --key redis-client.key \
          --tls \
          --cacert certs/rootCA.pem \
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;We've gone over the processes involved in creating keys and certificates for servers and client and setting up a Redis server, without and with client authentication. It's important to know that these &lt;em&gt;mkcert&lt;/em&gt; certificates we've generated have a limited life span, at which point you'll have to manually generate and install them again. So while they are great for your development processes, you'll want something more automated when it comes to production. Which we'll come to in a future article.&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>security</category>
    </item>
    <item>
      <title>Deno on Fly using Buildpacks</title>
      <dc:creator>Fly.io</dc:creator>
      <pubDate>Tue, 05 May 2020 15:09:41 +0000</pubDate>
      <link>https://dev.to/flyio/deno-on-fly-using-buildpacks-4pm</link>
      <guid>https://dev.to/flyio/deno-on-fly-using-buildpacks-4pm</guid>
      <description>&lt;p&gt;We really like &lt;a href="https://deno.land"&gt;Deno&lt;/a&gt; at &lt;a href="https://fly.io"&gt;Fly.io&lt;/a&gt;. It really is a better Node and we’d love people to build more with it. Our plan to aid in the adoption of Deno? “Let's make building and deploying Deno apps on Fly as simple as other languages”. Now, you can configure, build and deploy Deno code in just two commands, and we’d like to show you how we did it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s Deno?
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://deno.land/"&gt;deno.land&lt;/a&gt; website bills Deno as a secure runtime for JavaScript and TypeScript. In practice, it’s like working with a streamlined Node where TypeScript is the norm, with a solid Rust foundation. It also incorporates many of the lessons learned over Node’s development and growth. &lt;/p&gt;

&lt;p&gt;As we write this, Deno is heading rapidly towards its first 1.0 release candidate and we are really looking forward to a post-1.0 Deno. We believe as more people discover Deno’s elegance as a platform it’ll become much more popular. So how do you build a Deno app for Fly now?&lt;/p&gt;

&lt;h2&gt;
  
  
  Straight to the chase
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Name your app’s entry point  &lt;code&gt;server.ts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create your Fly app with &lt;code&gt;flyctl apps create --builder flyio/builder&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;flyctl deploy&lt;/code&gt; and watch it all build and deploy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s it. &lt;/p&gt;

&lt;h2&gt;
  
  
  Behind the builder
&lt;/h2&gt;

&lt;p&gt;The “chase” above was notably short. How? Let's look at what’s powering this build process. &lt;a href="https://fly.io/blog/powerbuilding-with-fly/"&gt;Fly’s builder support&lt;/a&gt; landed in early April and is based on the Cloud Native &lt;a href="https://buildpacks.io/"&gt;Buildpacks.io&lt;/a&gt; (CNB) specifications and implementations for building cloud native container images. &lt;/p&gt;

&lt;p&gt;A Cloud Native build is made up of stacks, builders and buildpacks which come together to make a repeatable build process, for different languages and frameworks. Used together, they deliver container images that are ready to run. For the Deno builder, we created our own stack, builder and buildpack to build Deno.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stacks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Everything in CNB is built on a “stack”, the base operating system in which the builders operate. For the Fly stack, we selected Ubuntu bionic, added in curl and unzip utilities (to support the Deno installer) and built the three images - base, build and run - which will be used in the subsequent steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Builders&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A builder in CNB is a container loaded with all the OS level tooling needed to construct an image. It’s fundamentally an OS image of some form. It’s not tied to any specific container platform such as Docker, it’s designed to run wherever you can run a container. &lt;/p&gt;

&lt;p&gt;That said, the Buildpacks.io developers do have the &lt;code&gt;pack&lt;/code&gt; commmand that uses Docker to run these containers - especially useful on Windows and macOS where there is no native Linux container support. &lt;/p&gt;

&lt;p&gt;The builder brings up a container running the build stack and then steps through the buildpacks associated with the builder to work out which build script to run in that container.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Buildpacks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The buildpacks are smaller bundles of scripts (or Go programs) which have two jobs, detect and build. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;detect&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The detect script tries to work out if the directory contents it has been given are appropriate to build with its build script. For example, a detect script for Ruby would look for a &lt;code&gt;Gemfile&lt;/code&gt; and go “aha! this is the song of my people! run my build script”. &lt;/p&gt;

&lt;p&gt;Now, Deno doesn't have an obvious packaging file like Ruby or well, anything else, due to it being so self-contained. So, we made a rule. If there’s a &lt;code&gt;server.ts&lt;/code&gt; file in the directory, we’ll assume it's a Deno application and signal to run our build script.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;build&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The build script runs in the Builder’s container and does the work of assembling tools and building the layers to create our image. In the case of our Deno buildpack, that includes downloading Deno into the image and running the Deno &lt;code&gt;deno cache&lt;/code&gt; command so all the dependencies are ready to run. Finally, it writes out a set of launch commands to start up the application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;permissions&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now, one thing Deno does is restrict access to resources by default. You have to specify which resources are available to any program with &lt;code&gt;--&lt;/code&gt;&lt;code&gt;allow-*&lt;/code&gt; command-line flags. Of course this is hard to pass through the command chain, so we don’t. What we do is add a &lt;code&gt;.permissions&lt;/code&gt; file, containing the command-line arguments you’d expect to be added to the command-line, to the directory with the code we’re building. In the example app, this file contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--allow-env --allow-net
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Allowing environment variable and network access. If there’s no &lt;code&gt;.permissions&lt;/code&gt; file, currently we default to no permission flags, in sync with the default Deno experience. You can incrementally add appropriate permissions to your app as you iterate your code. &lt;/p&gt;

&lt;p&gt;If you want to allow all access, you can put &lt;code&gt;-A&lt;/code&gt; into &lt;code&gt;.permissions&lt;/code&gt;, allow all access and sort out permissions later (well, that’s what you’ll say but you know it’ll slip and you’ll do a production deployment with it still in place.&lt;/p&gt;

&lt;p&gt;The arguments are added to the &lt;code&gt;deno&lt;/code&gt; command when the container and its launch file are built.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step by Step
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Creating an app&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So, let’s build a Deno app. We’re going to use &lt;a href="https://github.com/syumai/dinatra"&gt;dinatra&lt;/a&gt;, a Deno module which gives Sinatra-like capabilities to Deno apps. You’ll of course want to install Deno and create a directory for your app. Then make a &lt;code&gt;server.ts&lt;/code&gt; file and put this in it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import {
    app,
    get,
    post,
    redirect,
    contentType,
  } from "https://denopkg.com/syumai/dinatra@0.11.0/mod.ts";

  app(
    get("/hello", () =&amp;gt; `Hello`),
    get("/hello/:id", ({ params }) =&amp;gt; `Hello to ${params.id}`)
  );
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that’s an entire simple web server application. Don’t forget permissions though: create a &lt;code&gt;.permissions&lt;/code&gt; file with &lt;code&gt;--allow-net&lt;/code&gt; in it; all this application wants is network access.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Extra steps&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You may want to test your app before deploying it. You have two options. &lt;/p&gt;

&lt;p&gt;The first is to just run it before packaging it into a container image. &lt;/p&gt;

&lt;p&gt;Running the deno command with the permissions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;deno --allow-net server.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;will be all you need in that case&lt;/p&gt;

&lt;p&gt;The second is to package up the container image and run that image. You’ll need &lt;a href="https://www.docker.com/products/docker-desktop"&gt;Docker&lt;/a&gt; and Buildpacks.io's &lt;a href="https://buildpacks.io/docs/install-pack/"&gt;pack&lt;/a&gt; installed locally to do this. Use pack to create the image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pack build test-server-app --builder flyio/builder
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then use Docker to run the &lt;code&gt;test-server-app&lt;/code&gt; image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -p 8080:8080 test-server-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Deploying to Fly&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now we can create a Fly app for this code by running&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flyctl apps create --builder flyio/builder
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we can deploy it with &lt;code&gt;flyctl deploy&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;For Fly Buildpacks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We’ve made all the Buildpack code available in a &lt;a href="https://github.com/superfly/fly-builders"&gt;GitHub repository&lt;/a&gt; for anyone who wants to improve the process. We’ll be looking to add new buildpacks to it too, and enhance existing buildpacks. &lt;/p&gt;

&lt;p&gt;It’s worth noting that you can build your own local buildpack and use it with the fly-builder stack for local/test builds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For Deno on Fly&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We’re ready for your next Deno app on Fly, even if it’s your first. With a simple build and deploy process, it's easier than ever.&lt;/p&gt;

</description>
      <category>deno</category>
      <category>buildpacks</category>
      <category>cloudnative</category>
    </item>
    <item>
      <title>Making Datasets Fly with Datasette and Fly</title>
      <dc:creator>Fly.io</dc:creator>
      <pubDate>Mon, 30 Mar 2020 14:20:21 +0000</pubDate>
      <link>https://dev.to/flyio/making-datasets-fly-with-datasette-and-fly-2bg8</link>
      <guid>https://dev.to/flyio/making-datasets-fly-with-datasette-and-fly-2bg8</guid>
      <description>&lt;p&gt;&lt;em&gt;The creator of Datasette, the tool for Dataset sharing and exploration, has added Fly to the platforms you can use to publish and share data. We take a look at Datasette and show how well it works with Fly.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I've always liked finding a good dataset. With a background in databases and writing, I know a good dataset can bring a demo to life, be it a &lt;a href="https://data.cityofnewyork.us/Environment/2018-Central-Park-Squirrel-Census-Squirrel-Data/vfnx-vebw"&gt;census of Squirrels in Central Park&lt;/a&gt; or a &lt;a href="https://figshare.com/articles/Area-level_grocery_purchases/7796666"&gt;survey of grocery purchases in London&lt;/a&gt;. Datasets can also provide valuable foundations for citizen journalism and, under analysis, provide insights. &lt;/p&gt;

&lt;p&gt;The key to making these datasets work for people is making them accessible and available, which is where &lt;a href="https://github.com/simonw/datasette"&gt;Datasette&lt;/a&gt; comes in. It's a project by Simon Willison designed to make sharing datasets easy. It all hinges on SQLite - essentially, you load up an SQLite database with data and then hand it to Datasette which presents it through a web site, to the world.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, the data!
&lt;/h2&gt;

&lt;p&gt;I'm going to use the &lt;a href="https://data.cityofnewyork.us/Environment/2018-Central-Park-Squirrel-Census-Squirrel-Data/vfnx-vebw"&gt;New York Central Park Squirrel Census Data&lt;/a&gt; for my data source because squirrels rock. Go there and click the &lt;strong&gt;View Data&lt;/strong&gt; button to see the data presented in the very fine NYC OpenData viewer. &lt;/p&gt;

&lt;p&gt;I want the raw data though so I'll click on &lt;strong&gt;Export&lt;/strong&gt; and select &lt;strong&gt;CSV&lt;/strong&gt; which will kick off an immediate download. We now have a &lt;code&gt;2018_Central_Park_Squirrel_Census_-_Squirrel_Data.csv&lt;/code&gt; file to work with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making an Sqlite database
&lt;/h2&gt;

&lt;p&gt;Ensure you have SQLite3 installed on your system; it'll already be there on a macOS system as it is heavily used throughout the OS. If you run &lt;code&gt;sqlite3 filename.db&lt;/code&gt; the data will be persisted to that file rather than just held in memory, so let's begin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❯ sqlite3 squirrels.db
SQLite version 3.28.0 2019-04-15 14:49:49
Enter ".help" for usage hints.
sqlite&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the first thing we need to do is set the mode to CSV. If you look at the documentation, you'll see the &lt;code&gt;.mode&lt;/code&gt; command listed as setting the output mode. That's not quite completely true, it's also used as a hint to the import command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sqlite&amp;gt; .mode csv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we are ready to import with &lt;code&gt;.import&lt;/code&gt;. This command takes the CSV filename and a table name to import into. If the table isn't there, it'll use the first row in the CSV file to create the table columns and then import. (ProTip: If the table already exists it just imports everything, including the header row so always drop the table first).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sqlite&amp;gt; .import 2018_Central_Park_Squirrel_Census_-_Squirrel_Data.csv squirrels
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we can do a quick check on what actually got imported with  the &lt;code&gt;.schema&lt;/code&gt; command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sqlite&amp;gt; .schema squirrels
CREATE TABLE squirrels(
  "X" TEXT,
  "Y" TEXT,
  "Unique Squirrel ID" TEXT,
  "Hectare" TEXT,
  "Shift" TEXT,
  "Date" TEXT,
  "Hectare Squirrel Number" TEXT,
 ...
  "Lat/Long" TEXT,
  "Zip Codes" TEXT,
  "Community Districts" TEXT,
  "Borough Boundaries" TEXT,
  "City Council Districts" TEXT,
  "Police Precincts" TEXT
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a lot of columns about squirrels. Now I can exit sqlite3 and get ready to apply Datasette to the database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing Datasette
&lt;/h2&gt;

&lt;p&gt;There are a &lt;a href="https://datasette.readthedocs.io/en/stable/installation.html"&gt;couple of ways to install and run Datasette&lt;/a&gt;. I'm going to go with the one that is simplest for most developers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;make sure Python3 is installed (&lt;code&gt;brew install python3&lt;/code&gt; on macOS using &lt;a href="//homebrew.sh"&gt;HomeBrew&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pip3 install datasette&lt;/code&gt; - You may get an error on macOS as it stops the file being copied to a system directory. Don't worry, just repeat the command with &lt;code&gt;--user&lt;/code&gt; on the end and add the directory it suggests into your path.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And we're ready to test running &lt;code&gt;datasette squirrels.db&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❯ datasette squirrels.db
Serve! files=('squirrels.db',) (immutables=()) on port 8001
INFO:     Started server process [2812]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8001 (Press CTRL+C to quit)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Navigating to &lt;a href="http://127.0.0.1:8001"&gt;http://127.0.0.1:8001&lt;/a&gt; and clicking on the Squirrel table, we should see the squirrels data available:&lt;/p&gt;

&lt;p&gt;And clicking through you can start using Datasette's UI to compose views of particular &lt;a href="https://datasette.readthedocs.io/en/stable/facets.html"&gt;facets&lt;/a&gt; of the data, write your own SQL queries, export data as JSON or CSV or access the data through a REST/JSON API. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EwjSicDz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://fly.io/blog/2020-03-26/squirrels-index.png%3F1/2%26card%26center" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EwjSicDz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://fly.io/blog/2020-03-26/squirrels-index.png%3F1/2%26card%26center" alt="Local Datasette Index"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next stop is making it available to the world.&lt;/p&gt;

&lt;h2&gt;
  
  
  Publishing to Fly
&lt;/h2&gt;

&lt;p&gt;While other platforms are already built into Datasette, the Fly publishing element of Datasette is a new plugin, created by Datasette's developer. That means it has to be installed separately with &lt;code&gt;pip3 install datasette-publish-fly&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;With that installed, you can run&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;datasette publish fly squirrels.db --app squirrels
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--app&lt;/code&gt; flag lets you set the app name and it will be rejected if it clashes with an existing app. You may, if you are in multiple organizations, be asked to pick one of those too. Once you've done that, the publish command takes over, builds an image and deploys it onto the Fly platform. If you want to know what IP address and hostname the app is on, run &lt;code&gt;flyctl info -a &amp;lt;appname&amp;gt;&lt;/code&gt; like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❯ flyctl info -a squirrels

App
  Name     = squirrels
  Owner    = dj
  Version  = 0
  Status   = running
  Hostname = squirrels.fly.dev

Services
  PROTOCOL   PORTS
  TCP        80 =&amp;gt; 8080 [HTTP]
             443 =&amp;gt; 8080 [TLS, HTTP]

IP Addresses
  TYPE   ADDRESS                                CREATED AT
  v4     77.83.142.59                           23m21s ago
  v6     2a09:8280:1:7bc8:bf19:7779:aef7:8f18   23m21s ago
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that also tells us where we need to browse: &lt;code&gt;squirrels.fly.dev&lt;/code&gt;. We're online and we can dig down into a table view where we can compose queries.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dUKWugVS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://fly.io/blog/2020-03-26/squirrel-table.png%3Fcard" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dUKWugVS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://fly.io/blog/2020-03-26/squirrel-table.png%3Fcard" alt="Squirrel Table"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying with Plugins
&lt;/h2&gt;

&lt;p&gt;Datasette Plugins aren't just for publishing; there are a &lt;a href="https://datasette.readthedocs.io/en/stable/ecosystem.html#datasette-plugins"&gt;whole range of additional capabilities&lt;/a&gt; waiting to be slotted in. Take &lt;a href="https://github.com/simonw/datasette-cluster-map"&gt;datasette-cluster-map&lt;/a&gt;, for example. It looks for latitude and longitude columns in the data and turns the data into an interactive map using them. Let's see how we use this with Fly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tuning the tables
&lt;/h3&gt;

&lt;p&gt;The Squirrel data has X and Y coordinates which match Longitude and Latitude; we'll need to rename those columns first. Now, for a long time, sqlite lacked the ability to rename columns, so you'll find many workarounds on the web if you search. The good news is, though, that since 2018 you have been able to rename columns with the &lt;a href="https://sqlite.org/lang_altertable.html#altertabmvcol"&gt;&lt;code&gt;alter table rename column&lt;/code&gt;&lt;/a&gt; command. So I'll just load up the sqlite3 database and alter those columns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❯ sqlite3 squirrels.db
SQLite version 3.28.0 2019-04-15 14:49:49
Enter ".help" for usage hints.
sqlite&amp;gt; alter table squirrels rename column X to Longitude;
sqlite&amp;gt; alter table squirrels rename column Y to Latitude;
sqlite&amp;gt; .schema squirrels
CREATE TABLE squirrels(
  "Longitude" TEXT,
  "Latitude" TEXT,
...
sqlite&amp;gt; .exit
❯
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Run Locally Again
&lt;/h3&gt;

&lt;p&gt;I now need to install that plugin with &lt;code&gt;pip3&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❯ pip3 install datasette-cluster-map
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And run datasette locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;❯ datasette squirrels.db
Serve! files=('squirrels.db',) (immutables=()) on port 8001
INFO:     Started server process [39385]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     Uvicorn running on http://127.0.0.1:8001 (Press CTRL+C to quit)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And if we browse to that &lt;a href="http://localhost:8001/"&gt;http://localhost:8001/&lt;/a&gt; we'll see the index page as we did before. Now to deploy to Fly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploying and Plugins
&lt;/h3&gt;

&lt;p&gt;The difference here from when we previously published to Fly is that we have to list the plugins we need installed with our Datasette. The &lt;code&gt;--install&lt;/code&gt; flag takes care of that, so now I can publish to Fly with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;datasette publish fly squirrels.db --app squirrels-mapped --install datasette-cluster-map
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that will include the cluster-map plugin. If I now browse to "&lt;a href="https://squirrels-mapped.fly.dev"&gt;https://squirrels-mapped.fly.dev&lt;/a&gt;", and click in on the squirrels table:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Nv2-cfcc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://fly.io/blog/2020-03-26/squirrels-mapped-map.jpg%3Fcard" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Nv2-cfcc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://fly.io/blog/2020-03-26/squirrels-mapped-map.jpg%3Fcard" alt="Squirrels Mapped"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our view of the Squirrels data now includes a cluster map over Central Park that we can click in on and get a closer view. When sightings resolve to a single squirrel, you can hover over it to get all the details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Datasette and Fly
&lt;/h2&gt;

&lt;p&gt;So what does Fly add to Datasette? Well, as well as being super simple to deploy, you may have noticed that all the connections we're making are HTTPS secured, with Let's Encrypt certificates being generated automatically. If you want more, it's simple to use your own custom domain or hostname with your Fly/Datasette deployment. You can also deploy all around the world so your dataset is available where people need it to be. And there's also the edge networking/SSL termination which makes interaction that bit snappier. There are a whole lot more to explore in Datasette - &lt;a href="https://datasette.readthedocs.io/en/stable/"&gt;check out the documentation&lt;/a&gt; - and it's a great way to discover how you can make your applications Fly.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thanks to Simon Willison, not only for Datasette and the Fly plugin, but also for his feedback on this article. And to the Squirrels of New York's Central Park for taking part in the &lt;a href="https://thesquirrelcensus.com/"&gt;census&lt;/a&gt;. Want to learn more about Fly? Head over to our &lt;a href="https://dev.to/docs/"&gt;Fly Docs&lt;/a&gt; for lots more, including a &lt;a href="https://dev.to/docs/hands-on/start/"&gt;Hands On&lt;/a&gt; where you can get a free account and deploy your first app today.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>datasets</category>
      <category>sql</category>
      <category>publish</category>
    </item>
  </channel>
</rss>
