<?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: Ian Eyberg</title>
    <description>The latest articles on DEV Community by Ian Eyberg (@eyberg).</description>
    <link>https://dev.to/eyberg</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%2F131131%2F3fabbd12-30d2-4e79-a347-1f4e4de9b287.jpeg</url>
      <title>DEV Community: Ian Eyberg</title>
      <link>https://dev.to/eyberg</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/eyberg"/>
    <language>en</language>
    <item>
      <title>Running Linux Programs as Unikernels on macOS</title>
      <dc:creator>Ian Eyberg</dc:creator>
      <pubDate>Mon, 10 Feb 2020 21:17:34 +0000</pubDate>
      <link>https://dev.to/eyberg/running-linux-programs-as-unikernels-on-macos-9dd</link>
      <guid>https://dev.to/eyberg/running-linux-programs-as-unikernels-on-macos-9dd</guid>
      <description>&lt;p&gt;Someone emailed me the other day and was having some trouble getting&lt;br&gt;
their program to work under OPS.&lt;/p&gt;

&lt;p&gt;He had downloaded a binary release from github and knowing that OPS only runs linux binaries chose the linux version. Further, there are numerous&lt;br&gt;
examples of people running linux programs under ops on osx. Hell, just do a&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;ops pkg list
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;for a short list.&lt;/p&gt;

&lt;p&gt;However, for some reason it just wasn't working for him.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;user@users-MacBook-Pro Desktop % ops run solana-validator    
&lt;span class="k"&gt;*&lt;/span&gt;errors.errorString libstdc++.so.6: file does not exist
/Users/eyberg/go/src/github.com/nanovms/ops/lepton/ldd_darwin.go:100 &lt;span class="o"&gt;(&lt;/span&gt;0x4c55779&lt;span class="o"&gt;)&lt;/span&gt;
/Users/eyberg/go/src/github.com/nanovms/ops/lepton/ldd_darwin.go:129
&lt;span class="o"&gt;(&lt;/span&gt;0x4c5607d&lt;span class="o"&gt;)&lt;/span&gt;
/Users/eyberg/go/src/github.com/nanovms/ops/lepton/image.go:261
&lt;span class="o"&gt;(&lt;/span&gt;0x4c52d0e&lt;span class="o"&gt;)&lt;/span&gt;
/Users/eyberg/go/src/github.com/nanovms/ops/lepton/image.go:24
&lt;span class="o"&gt;(&lt;/span&gt;0x4c50f5f&lt;span class="o"&gt;)&lt;/span&gt;
/Users/eyberg/go/src/github.com/nanovms/ops/cmd/run.go:15 &lt;span class="o"&gt;(&lt;/span&gt;0x4ca4189&lt;span class="o"&gt;)&lt;/span&gt;
/Users/eyberg/go/src/github.com/nanovms/ops/cmd/run.go:118 &lt;span class="o"&gt;(&lt;/span&gt;0x4ca4f75&lt;span class="o"&gt;)&lt;/span&gt;
/Users/eyberg/go/pkg/mod/github.com/spf13/cobra@v0.0.5/command.go:830
&lt;span class="o"&gt;(&lt;/span&gt;0x4c8bf3e&lt;span class="o"&gt;)&lt;/span&gt;
/Users/eyberg/go/pkg/mod/github.com/spf13/cobra@v0.0.5/command.go:914
&lt;span class="o"&gt;(&lt;/span&gt;0x4c8cb5b&lt;span class="o"&gt;)&lt;/span&gt;
/Users/eyberg/go/pkg/mod/github.com/spf13/cobra@v0.0.5/command.go:864
&lt;span class="o"&gt;(&lt;/span&gt;0x4ca8d88&lt;span class="o"&gt;)&lt;/span&gt;
/Users/eyberg/go/pkg/mod/github.com/spf13/cobra@v0.0.5/command.go:864
&lt;span class="o"&gt;(&lt;/span&gt;0x4ca8d83&lt;span class="o"&gt;)&lt;/span&gt;
/usr/local/go/src/runtime/proc.go:200 &lt;span class="o"&gt;(&lt;/span&gt;0x402f57c&lt;span class="o"&gt;)&lt;/span&gt;
  main: &lt;span class="k"&gt;return&lt;/span&gt;
/usr/local/go/src/runtime/asm_amd64.s:1337 &lt;span class="o"&gt;(&lt;/span&gt;0x405a671&lt;span class="o"&gt;)&lt;/span&gt;
  goexit: GLOBL shifts&amp;lt;&lt;span class="o"&gt;&amp;gt;(&lt;/span&gt;SB&lt;span class="o"&gt;)&lt;/span&gt;,RODATA,&lt;span class="nv"&gt;$256&lt;/span&gt;

panic: libstdc++.so.6: file does not exist
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;After I saw the word mac and then libstdc++ I knew immediately what was wrong but I also realized that it might not be readily apparent to many people and so this is what this blogpost is about.&lt;/p&gt;

&lt;p&gt;The binary in question is dynamically linked. What this means is that&lt;br&gt;
there are several libraries that the dynamic loader (ld) will try and&lt;br&gt;
load at runtime versus statically linked where all the libraries are&lt;br&gt;
packaged inside the binary. This leads to a fatter binary but you can be&lt;br&gt;
assured that everything is present when needed. I won't jump into a&lt;br&gt;
static/dynamic flamewar as they each have their own usecases.&lt;/p&gt;

&lt;p&gt;I should also point out that normally you can't run linux binaries directly on a mac - this is actually a uniquely interesting unikernel aspect as we don't need to boot up vagrant or a linux vm. Traditional virtualization software virtualizes the operating system. You can see using a tool like &lt;a href="https://ops.city"&gt;OPS&lt;/a&gt; as virtualizing the application instead.&lt;/p&gt;

&lt;p&gt;One of the reasons I decided to write this post was not just the email but I've noticed in the past few years I've seen quite a lot of people that use containers and go to refer to their programs as statically linked when it is in fact absolutely not.&lt;/p&gt;

&lt;p&gt;Let's look at this example go webserver:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;package main

import &lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s2"&gt;"net/http"&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;

func main&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    http.HandleFunc&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;, func&lt;span class="o"&gt;(&lt;/span&gt;w http.ResponseWriter, r &lt;span class="k"&gt;*&lt;/span&gt;http.Request&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        fmt.Fprintf&lt;span class="o"&gt;(&lt;/span&gt;w, &lt;span class="s2"&gt;"Welcome to my website!"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;})&lt;/span&gt;

    fs :&lt;span class="o"&gt;=&lt;/span&gt; http.FileServer&lt;span class="o"&gt;(&lt;/span&gt;http.Dir&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"static/"&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
    http.Handle&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/static/"&lt;/span&gt;, http.StripPrefix&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/static/"&lt;/span&gt;, fs&lt;span class="o"&gt;))&lt;/span&gt;

    http.ListenAndServe&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;":8080"&lt;/span&gt;, nil&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Fairly simple - all it does is listen on 8080 and serve up requests. If we compile it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;go build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We see that by default it will be dynamically linked against a few libs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eyberg@box:~/z$ ldd z
        linux-vdso.so.1 (0x00007ffe8b1db000)
        libpthread.so.0 =&amp;gt; /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f2e091f4000)
        libc.so.6 =&amp;gt; /lib/x86_64-linux-gnu/libc.so.6 (0x00007f2e08e03000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f2e09413000)
eyberg@box:~/z$ file z
z: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/l, not stripped
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;VDSO gives us a fast clock. Libpthread gives us threads. Libc contains everything you'd find in section 3 of the man pages and finally LD is our loader.&lt;/p&gt;

&lt;p&gt;You &lt;strong&gt;can&lt;/strong&gt; link your go programs statically via something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;eyberg@box:~/z&lt;span class="nv"&gt;$ &lt;/span&gt;go build  &lt;span class="nt"&gt;-buildmode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pie &lt;span class="nt"&gt;-ldflags&lt;/span&gt; &lt;span class="s2"&gt;"-linkmode external -extldflags -static"&lt;/span&gt;
&lt;span class="c"&gt;# _/home/eyberg/z&lt;/span&gt;
/tmp/go-link-057347370/000004.o: In &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;_cgo_7e1b3c2abc8d_C2func_getaddrinfo&lt;span class="s1"&gt;':
/tmp/go-build/cgo-gcc-prolog:57: warning: Using '&lt;/span&gt;getaddrinfo&lt;span class="s1"&gt;' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking
eyberg@box:~/z$ ldd z
        not a dynamic executable
eyberg@box:~/z$ file z
z: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=0671d22e52baf812ed9f500a8d9ee7848b8c809e, not stripped
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Both ldd &amp;amp;&amp;amp; file now report that it is statically linked. However, you'll notice (at least in go 1.12) it now outputs a message stating that getaddrinfo is still required! That's cause even though ldd output and file is telling us that it is indeed static we still rely on libnss for dns. You can force go to use its built in resolver instead.&lt;/p&gt;

&lt;p&gt;Getaddrinfo is found here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;eyberg@box:~/s/solana-release/bin&lt;span class="nv"&gt;$ &lt;/span&gt;readelf &lt;span class="nt"&gt;--dyn-syms&lt;/span&gt; /lib/x86_64-linux-gnu/libc-2.27.so  | &lt;span class="nb"&gt;grep &lt;/span&gt;getaddr
   873: 0000000000107bc0  3261 FUNC    GLOBAL DEFAULT   13 getaddrinfo@@GLIBC_2.2.5
  1601: 00000000001413c0    43 FUNC    GLOBAL DEFAULT   13 inet6_rth_getaddr@@GLIBC_2.5
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You'll note that your libraries are probably stripped which means you can't use something like nm to find the symbols but readelf surfaces them up in the .dynsym section.&lt;/p&gt;

&lt;p&gt;Also, even that is not enough - you'll probably make use of various functions found in these files as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eyberg@box:~/z$ ls /lib/x86_64-linux-gnu/libnss_files.so.2
/lib/x86_64-linux-gnu/libnss_files.so.2
eyberg@box:~/z$ ls /lib/x86_64-linux-gnu/libnss_dns.so.2
/lib/x86_64-linux-gnu/libnss_dns.so.2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The point here is that just sticking a go program in a container does not inherently make your binary 'statically linked' and the problem is that, that is not what a lot of people have been stating and this source of&lt;br&gt;
confusion is probably what leads to misunderstandings such as this.&lt;/p&gt;

&lt;p&gt;So let's go back to our example at the beginning of the article.&lt;/p&gt;

&lt;p&gt;The program in question is the &lt;a href="https://github.com/solana-labs/solana"&gt;solana blockchain&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can download and unzip like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wget https://github.com/solana-labs/solana/releases/download/v0.23.2/solana-release-x86_64-unknown-linux-gnu.tar.bz2
bunzip2 solana-release-x86_64-unknown-linux-gnu.tar.bz2
tar xf solana-release-x86_64-unknown-linux-gnu.tar
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You can see from the ldd output that it is trying to load a library that is dynamically linked (libstdc++) and that's what our error in the trace was.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;eyberg@box:~/s/solana-release/bin&lt;span class="nv"&gt;$ &lt;/span&gt;ldd solana
        linux-vdso.so.1 &lt;span class="o"&gt;(&lt;/span&gt;0x00007ffe338df000&lt;span class="o"&gt;)&lt;/span&gt;
        libc.so.6 &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; /lib/x86_64-linux-gnu/libc.so.6 &lt;span class="o"&gt;(&lt;/span&gt;0x00007f06976a8000&lt;span class="o"&gt;)&lt;/span&gt;
        /lib64/ld-linux-x86-64.so.2 &lt;span class="o"&gt;(&lt;/span&gt;0x00007f06984a2000&lt;span class="o"&gt;)&lt;/span&gt;
        libdl.so.2 &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; /lib/x86_64-linux-gnu/libdl.so.2 &lt;span class="o"&gt;(&lt;/span&gt;0x00007f06974a4000&lt;span class="o"&gt;)&lt;/span&gt;
        librt.so.1 &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; /lib/x86_64-linux-gnu/librt.so.1 &lt;span class="o"&gt;(&lt;/span&gt;0x00007f069729c000&lt;span class="o"&gt;)&lt;/span&gt;
        libpthread.so.0 &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; /lib/x86_64-linux-gnu/libpthread.so.0 &lt;span class="o"&gt;(&lt;/span&gt;0x00007f069707d000&lt;span class="o"&gt;)&lt;/span&gt;
        libgcc_s.so.1 &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; /lib/x86_64-linux-gnu/libgcc_s.so.1 &lt;span class="o"&gt;(&lt;/span&gt;0x00007f0696e65000&lt;span class="o"&gt;)&lt;/span&gt;
        libm.so.6 &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; /lib/x86_64-linux-gnu/libm.so.6 &lt;span class="o"&gt;(&lt;/span&gt;0x00007f0696ac7000&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;As you can see this program works out of the box on linux.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;eyberg@box:~/s/solana-release/bin&lt;span class="nv"&gt;$ &lt;/span&gt;ops run &lt;span class="nt"&gt;-c&lt;/span&gt; config.json solana-validator
&lt;span class="o"&gt;[&lt;/span&gt;solana-validator &lt;span class="nt"&gt;--ledger&lt;/span&gt; /]
booting /home/eyberg/.ops/images/solana-validator.img ...
qemu-system-x86_64: warning: TCG doesn&lt;span class="s1"&gt;'t support requested feature: CPUID.01H:ECX.vmx [bit 5]
assigned: 10.0.2.15
solana-validator 0.23.2
log file: solana-validator-7wgZy3ozTRyvLtRN5o3Xh6m5aEvEo2XBxP5yPfaCny1t-20200210-204507.log

eyberg@box:~/s/solana-release/bin$ cat config.json
{
  "Args": ["solana-validator", "--ledger", "/"]
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This is all fine but if you want to run it on osx you'll need to do one&lt;br&gt;
of two things:&lt;/p&gt;

&lt;p&gt;1) Either build from source and statically link it (so ldd doesn't show&lt;br&gt;
output).&lt;/p&gt;

&lt;p&gt;or&lt;/p&gt;

&lt;p&gt;2) Download the libraries it's linked to and manually add them to the&lt;br&gt;
filesystem.&lt;/p&gt;

&lt;p&gt;The reason this works out of the box on linux is that &lt;a href="https://ops.city"&gt;OPS&lt;/a&gt; looks at and&lt;br&gt;
try to load the libraries as we build the disk image. This isn't&lt;br&gt;
possible on a mac cause mac uses mach-o which is a different format that &lt;a href="https://github.com/nanovms/nanos"&gt;Nanos&lt;/a&gt; does not and probably won't ever support as &lt;a href="https://github.com/nanovms/nanos"&gt;nanos&lt;/a&gt; is explicitly designed to run linux server-side programs.&lt;/p&gt;

&lt;p&gt;For example when we build common packages like nginx or node, you'll see from the &lt;a href="https://github.com/nanovms/ops"&gt;ops&lt;/a&gt; output that all required libraries are placed on the filesystem in a known location.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜  ~    ops pkg contents node_v13.6.0
File :/node
File :/package.manifest
Dir :/sysroot
Dir :/sysroot/lib
Dir :/sysroot/lib/x86_64-linux-gnu
File :/sysroot/lib/x86_64-linux-gnu/libc.so.6
File :/sysroot/lib/x86_64-linux-gnu/libdl.so.2
File :/sysroot/lib/x86_64-linux-gnu/libgcc_s.so.1
File :/sysroot/lib/x86_64-linux-gnu/libm.so.6
File :/sysroot/lib/x86_64-linux-gnu/libnss_dns.so.2
File :/sysroot/lib/x86_64-linux-gnu/libnss_files.so.2
File :/sysroot/lib/x86_64-linux-gnu/libpthread.so.0
Dir :/sysroot/lib64
File :/sysroot/lib64/ld-linux-x86-64.so.2
Dir :/sysroot/proc
File :/sysroot/proc/meminfo
Dir :/sysroot/usr
Dir :/sysroot/usr/lib
Dir :/sysroot/usr/lib/x86_64-linux-gnu
File :/sysroot/usr/lib/x86_64-linux-gnu/libstdc++.so.6
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Without getting into the semantics of building your own package &lt;a href="https://github.com/nanovms/ops/blob/master/PACKAGES.md"&gt;which you can find here&lt;/a&gt; the easiest way to do this with your own app is in your project directory you can create a directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir -p usr/lib
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;then include it in your config.json that you pass to ops when it builds your image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"Dirs"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"usr"&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;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ops run -c config.json myprogram
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;After that you can always grab the image that was created in ~/.ops/images.&lt;/p&gt;

&lt;p&gt;Hope this clears up any confusion.&lt;/p&gt;

</description>
      <category>unikernels</category>
      <category>rust</category>
      <category>blockchain</category>
      <category>linux</category>
    </item>
    <item>
      <title>Serverless Security with Unikernels</title>
      <dc:creator>Ian Eyberg</dc:creator>
      <pubDate>Thu, 14 Nov 2019 20:29:31 +0000</pubDate>
      <link>https://dev.to/eyberg/serverless-security-with-unikernels-eb0</link>
      <guid>https://dev.to/eyberg/serverless-security-with-unikernels-eb0</guid>
      <description>&lt;p&gt;Security is one of those topics where on one hand you see a lot of passionate developers that get upset whenever there is a new data breach (and those seem to be happening on the daily), yet on the other hand there is a very large skills gap on understanding how hackers (the bad kind) think, what makes them tick and most importantly - how they operate.&lt;/p&gt;

&lt;p&gt;I think it's important developers start thinking about security in a&lt;br&gt;
more holistic manner.&lt;/p&gt;

&lt;p&gt;Let me give you an example. I was talking to a vp of eng the other day that said they are rather good on security cause all of their instances are inside a VPC. I agreed that was a good approach versus exposing everything on the internet but then I brought up the &lt;a href="https://ejj.io/blog/capital-one" rel="noopener noreferrer"&gt;Capital One hack&lt;/a&gt; and the &lt;a href="https://arstechnica.com/information-technology/2019/09/doordash-hack-spills-loads-of-data-for-4-9-million-people/" rel="noopener noreferrer"&gt;Door Dash hack&lt;/a&gt; and many others that occurred just this year. You can bet that if someone was only alerted 4-5 months after an attack such as in the case of DoorDash the miscreants have been all up and down those servers. Now exploiting a SSRF (server side request forgery) vulnerability is one thing but escalating the attack into the point where you have landed a shell inside a vpc is where this thinking falls apart. &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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F8o40c4mvoohx09666jgh.gif" 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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F8o40c4mvoohx09666jgh.gif" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Why is that?&lt;/p&gt;

&lt;p&gt;At the end of the day attackers don't care about what exploit or what vulnerability they are using to get onto your server. The only care about getting onto your server to run &lt;em&gt;their programs&lt;/em&gt;. For example cryptojacking attacks like the one that afflicted &lt;a href="https://arstechnica.com/information-technology/2018/02/tesla-cloud-resources-are-hacked-to-run-cryptocurrency-mining-malware/" rel="noopener noreferrer"&gt;Tesla&lt;/a&gt; are very popular nowadays cause unlike ransomware you don't have to wait to get paid - it just starts making money immediately! At the end of the day it doesn't matter that Tesla had exposed kubernetes to the world the attackers just wanted to mine some monero - they could care less how they broke in. This is the point.&lt;/p&gt;

&lt;p&gt;I'm going to show you real life attacks on Google Cloud here in a bit but first I want to set some expectations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Problem with Multiple Processes (or 'Just Use Threads')&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most attacks today rely on the capability of running &lt;em&gt;other&lt;/em&gt; programs on a given server/instance/container/etc. If you can't do that because fork/execve and friends have been seccomp'd out the attack has gotten progressively harder cause now you have to start doing more exotic attacks using things like rop gadgets. However, at the end of the day the end desire remains the same - unfettered access to run whatever program the attacker wants so typically the end goal there is to pop a shell.&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fb44d5wv83lf5h3ofcut6.png" 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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fb44d5wv83lf5h3ofcut6.png" alt="child process"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Not being a day to day js developer I did a quick search on github to see how popular forking a new process might be. This picture shows that it definitely is not unpopular.&lt;/p&gt;

&lt;p&gt;The recent paper &lt;a href="https://www.microsoft.com/en-us/research/uploads/prod/2019/04/fork-hotos19.pdf" rel="noopener noreferrer"&gt;A fork() in the road&lt;/a&gt; argues very well that we should not be using fork - at all in 2020. We have had native threads since ~2000 in Linux (yes, 20 years ago).&lt;/p&gt;

&lt;p&gt;In the past there wasn't a strong demand to get rid of it because Linux itself was designed to run on real machines - not virtual ones. This is important to point out cause how else would you run other programs on the same physical server? However, that proposition can now be re-examined at least for cloud computing use cases which are entirely built on virtual machines.&lt;/p&gt;

&lt;p&gt;For languages such as Java and Go you get threading out of the box so you can have as much performance as you have threads/cores available. For the interpreted language class such as Javascript and Ruby it's been common to stick X application servers behind a load balancer/reverse proxy to scale up. At the end of the day you get the exact same vCPU that you buy regardless. If you've got one vcpu user-land threads, async, and such might help you out some but forking off a half dozen worker processes won't - then you are just &lt;a href="https://www.i3s.unice.fr/~jplozi/wastedcores/files/extended_talk.pdf" rel="noopener noreferrer"&gt;fighting the operating system scheduler&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Serverless Security&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Serverless is clearly a desire for many developers today that don't wish to manage and run infrastructure. That makes sense as we keep pumping out tremendous amounts of software and devops salaries, at least in my neck of the woods (SF), are through the roof. Unfortunately, a lot of the status quo serverless offerings are built on top of popular cloud services leading to vendor lockin.&lt;/p&gt;

&lt;p&gt;Unikernels are a fresh set of eyes of looking at this problem space as they allow one to deploy the same set of code to any number of vendors using tried and true vms as their base artifact, albeit not the types of vms you might be used to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Running Node the Old Way&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's show how you might normally provision this javascript webserver. (and yes I understand that this would be automated but it's the same thing -- work with me here) First we spin up an instance. Ok, nothing abnormal here.&lt;/p&gt;

&lt;p&gt;Then we ssh in. Wait - hold on.&lt;/p&gt;

&lt;p&gt;Right off the bat we are explicitly allowing the concept of users to jump into an instance and run arbitrary commands. In fact every single configuration management tool out there including terraform, puppet, and chef are explicitly built on this concept which is odious from the start.&lt;/p&gt;

&lt;p&gt;Ok, let's continue.&lt;/p&gt;

&lt;p&gt;Once we are on the instance we install node.js:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eyberg@instance-1:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;nodejs
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following additional packages will be installed:
  libicu57 libuv1
The following NEW packages will be installed:
  libicu57 libuv1 nodejs
0 upgraded, 3 newly installed, 0 to remove and 0 not upgraded.
Need to get 11.2 MB of archives.
After this operation, 45.2 MB of additional disk space will be used.
Do you want to &lt;span class="k"&gt;continue&lt;/span&gt;? &lt;span class="o"&gt;[&lt;/span&gt;Y/n]
Get:1 http://deb.debian.org/debian stretch/main amd64 libicu57 amd64 57.1-6+deb9u3 &lt;span class="o"&gt;[&lt;/span&gt;7,705 kB]
Get:2 http://deb.debian.org/debian stretch/main amd64 libuv1 amd64 1.9.1-3 &lt;span class="o"&gt;[&lt;/span&gt;84.4 kB]
Get:3 http://deb.debian.org/debian stretch/main amd64 nodejs amd64 4.8.2~dfsg-1 &lt;span class="o"&gt;[&lt;/span&gt;3,440 kB]
Fetched 11.2 MB &lt;span class="k"&gt;in &lt;/span&gt;0s &lt;span class="o"&gt;(&lt;/span&gt;42.9 MB/s&lt;span class="o"&gt;)&lt;/span&gt;
Selecting previously unselected package libicu57:amd64.
&lt;span class="o"&gt;(&lt;/span&gt;Reading database ... 37215 files and directories currently installed.&lt;span class="o"&gt;)&lt;/span&gt;
Preparing to unpack .../libicu57_57.1-6+deb9u3_amd64.deb ...
Unpacking libicu57:amd64 &lt;span class="o"&gt;(&lt;/span&gt;57.1-6+deb9u3&lt;span class="o"&gt;)&lt;/span&gt; ...
Selecting previously unselected package libuv1:amd64.
Preparing to unpack .../libuv1_1.9.1-3_amd64.deb ...
Unpacking libuv1:amd64 &lt;span class="o"&gt;(&lt;/span&gt;1.9.1-3&lt;span class="o"&gt;)&lt;/span&gt; ...
Selecting previously unselected package nodejs.
Preparing to unpack .../nodejs_4.8.2~dfsg-1_amd64.deb ...
Unpacking nodejs &lt;span class="o"&gt;(&lt;/span&gt;4.8.2~dfsg-1&lt;span class="o"&gt;)&lt;/span&gt; ...
Setting up libuv1:amd64 &lt;span class="o"&gt;(&lt;/span&gt;1.9.1-3&lt;span class="o"&gt;)&lt;/span&gt; ...
Setting up libicu57:amd64 &lt;span class="o"&gt;(&lt;/span&gt;57.1-6+deb9u3&lt;span class="o"&gt;)&lt;/span&gt; ...
Processing triggers &lt;span class="k"&gt;for &lt;/span&gt;libc-bin &lt;span class="o"&gt;(&lt;/span&gt;2.24-11+deb9u4&lt;span class="o"&gt;)&lt;/span&gt; ...
Processing triggers &lt;span class="k"&gt;for &lt;/span&gt;man-db &lt;span class="o"&gt;(&lt;/span&gt;2.7.6.1-2&lt;span class="o"&gt;)&lt;/span&gt; ...
Setting up nodejs &lt;span class="o"&gt;(&lt;/span&gt;4.8.2~dfsg-1&lt;span class="o"&gt;)&lt;/span&gt; ...
update-alternatives: using /usr/bin/nodejs to provide /usr/bin/js &lt;span class="o"&gt;(&lt;/span&gt;js&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in &lt;/span&gt;auto mode
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice something strange? That's right. It didn't matter that my user is a non-root user - I could immediately 'sudo' my way to doing whatever I wanted on the instance.&lt;/p&gt;

&lt;p&gt;That whole concept of 'least privilege' and 'user separation' that security devs like to talk about is by default on many servers not present.&lt;/p&gt;

&lt;p&gt;Unfortunately, as soon as we do that we realize that Debian 9 (the first instance that Google offered to give us comes with node version 4.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eyberg@instance-1:~&lt;span class="nv"&gt;$ &lt;/span&gt;nodejs &lt;span class="nt"&gt;--version&lt;/span&gt;
v4.8.2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now our options are to either trash this instance or download a tarball. Let's go for that other option (even though knowing that if someone else touches this instance it might cause problems down the road).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eyberg@instance-1:~&lt;span class="nv"&gt;$ &lt;/span&gt;wget https://nodejs.org/dist/v12.13.0/node-v12.13.0-linux-x64.tar.xz
&lt;span class="nt"&gt;--2019-11-14&lt;/span&gt; 18:08:59--  https://nodejs.org/dist/v12.13.0/node-v12.13.0-linux-x64.tar.xz
Resolving nodejs.org &lt;span class="o"&gt;(&lt;/span&gt;nodejs.org&lt;span class="o"&gt;)&lt;/span&gt;... 104.20.23.46, 104.20.22.46, 2606:4700:10::6814:172e, ...
Connecting to nodejs.org &lt;span class="o"&gt;(&lt;/span&gt;nodejs.org&lt;span class="o"&gt;)&lt;/span&gt;|104.20.23.46|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 14055156 &lt;span class="o"&gt;(&lt;/span&gt;13M&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;application/x-xz]
Saving to: ‘node-v12.13.0-linux-x64.tar.xz’

node-v12.13.0-linux-x64.tar.xz                     100%[&lt;span class="o"&gt;================================================================================================================&amp;gt;]&lt;/span&gt;  13.40M  &lt;span class="nt"&gt;--&lt;/span&gt;.-KB/s    &lt;span class="k"&gt;in &lt;/span&gt;0.1s

2019-11-14 18:08:59 &lt;span class="o"&gt;(&lt;/span&gt;114 MB/s&lt;span class="o"&gt;)&lt;/span&gt; - ‘node-v12.13.0-linux-x64.tar.xz’ saved &lt;span class="o"&gt;[&lt;/span&gt;14055156/14055156]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eyberg@instance-1:~&lt;span class="nv"&gt;$ &lt;/span&gt;unxz node-v12.13.0-linux-x64.tar.xz
eyberg@instance-1:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;tar &lt;/span&gt;xf node-v12.13.0-linux-x64.tar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Let's jump into the code!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What this next snippet does is pop a webserver on that offers two urls to list the contents of a directory. One is a lot safer than the other as we'll soon find out. (Again, I'm not a js dev so excuse the ugliness of the code.)&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;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&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;http&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;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&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;fs&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;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&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;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;exec&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&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;child_process&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;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/safe&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="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readdirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&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="nf"&gt;writeHead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&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;Content-Type&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;text/plain&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="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&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="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="k"&gt;try&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;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ls&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;resbody&lt;/span&gt; &lt;span class="o"&gt;=&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;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// this is *unsafe*&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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="nx"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;resbody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="nx"&gt;resbody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stdout&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="nf"&gt;writeHead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&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;Content-Type&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;text/plain&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="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resbody&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&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="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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="nf"&gt;writeHead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&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;Content-Type&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;text/plain&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="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&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="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.0.0.0&lt;/span&gt;&lt;span class="dl"&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="nf"&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;Server running at http://127.0.0.1:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's run our program:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eyberg@instance-1:~/node-v12.13.0-linux-x64/bin&lt;span class="nv"&gt;$ &lt;/span&gt;./node bob.js
Server running at http://127.0.0.1:80/
events.js:187
      throw er&lt;span class="p"&gt;;&lt;/span&gt; // Unhandled &lt;span class="s1"&gt;'error'&lt;/span&gt; event
      ^

Error: listen EACCES: permission denied 0.0.0.0:80
    at Server.setupListenHandle &lt;span class="o"&gt;[&lt;/span&gt;as _listen2] &lt;span class="o"&gt;(&lt;/span&gt;net.js:1283:19&lt;span class="o"&gt;)&lt;/span&gt;
    at listenInCluster &lt;span class="o"&gt;(&lt;/span&gt;net.js:1348:12&lt;span class="o"&gt;)&lt;/span&gt;
    at doListen &lt;span class="o"&gt;(&lt;/span&gt;net.js:1487:7&lt;span class="o"&gt;)&lt;/span&gt;
    at processTicksAndRejections &lt;span class="o"&gt;(&lt;/span&gt;internal/process/task_queues.js:81:21&lt;span class="o"&gt;)&lt;/span&gt;
Emitted &lt;span class="s1"&gt;'error'&lt;/span&gt; event on Server instance at:
    at emitErrorNT &lt;span class="o"&gt;(&lt;/span&gt;net.js:1327:8&lt;span class="o"&gt;)&lt;/span&gt;
    at processTicksAndRejections &lt;span class="o"&gt;(&lt;/span&gt;internal/process/task_queues.js:80:21&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  code: &lt;span class="s1"&gt;'EACCES'&lt;/span&gt;,
  errno: &lt;span class="s1"&gt;'EACCES'&lt;/span&gt;,
  syscall: &lt;span class="s1"&gt;'listen'&lt;/span&gt;,
  address: &lt;span class="s1"&gt;'0.0.0.0'&lt;/span&gt;,
  port: 80
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Oh no! We forgot ports under 1024 are 'privileged'. Well no problem here - cause sudo make me a sandwich right?&lt;/p&gt;

&lt;p&gt;We have gone from bad to worse. A sane setup would probably have a frontend proxy sitting in front of this that can drop privileges after getting setup and forwarding on the request but now you might need to call in your devops person huh?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eyberg@instance-1:~/node-v12.13.0-linux-x64/bin&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;su
root@instance-1:/home/eyberg/node-v12.13.0-linux-x64/bin# ./node bob.js
Server running at http://127.0.0.1:80/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok, let's hit it up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;➜  ~  curl &lt;span class="nt"&gt;-XGET&lt;/span&gt; http://34.68.46.143/
bob.js
node
npm
npx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well - that works but is it safe?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;➜  ~  curl &lt;span class="nt"&gt;-XGET&lt;/span&gt; http://34.68.46.143/?cmd&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"touch%20tmp"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This first query passes in the command "touch tmp" which creates a new file in that directory - bad news bears. The %20 you might recognize as the url encoding for the space character.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;➜  ~  curl &lt;span class="nt"&gt;-XGET&lt;/span&gt; http://34.68.46.143/
bob.js
node
npm
npx
tmp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we can see, we can run arbitrary commands on our end server and worse it's running as root.&lt;/p&gt;

&lt;p&gt;This is a very oftenly abused software development pattern called 'shelling out'. There is almost never any good reason to do this and if you have code linters or static analysis setup on your ci there's a good chance it'll flag it or whoever is reviewing your PRs should.&lt;/p&gt;

&lt;p&gt;Now if we refactor the offending command injection into the '/safe'' equivalent we might get this instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;➜  ~  curl &lt;span class="nt"&gt;-XGET&lt;/span&gt; http://34.68.46.143/safe
bin,boot,dev,etc,home,initrd.img,initrd.img.old,lib,lib64,lost+found,media,mnt,opt,proc,root,run,sbin,srv,sys,tmp,usr,var,vmlinuz,vmlinuz.old
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Leaking out your root filesystem probably isn't the best thing to do but at least you aren't injecting commands anymore.&lt;/p&gt;

&lt;p&gt;Now, this is just one 41 line program here but this &lt;em&gt;is&lt;/em&gt; a full blown linux system. Let's see what else is on here before we retire this example.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attack Surface&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Envision Normandy 1944.&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F2w9wj5tct8yxzwb7yexm.jpg" 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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F2w9wj5tct8yxzwb7yexm.jpg" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The attack surface when we talk about linux systems is the amount of utter crap that we can attack.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@instance-1:~# find / &lt;span class="nt"&gt;-type&lt;/span&gt; f | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
76369
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;76,000 files! Just to run a 41 line javascript program?&lt;/p&gt;

&lt;p&gt;I wonder how many shared libraries we have on this system?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@instance-1:~# find / &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="nt"&gt;-regex&lt;/span&gt; &lt;span class="s2"&gt;".*&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;so.*"&lt;/span&gt; | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
751
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;750?? If we check out node we can see there are only 8 explicitly linked to node - why do we want/need the rest?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@instance-1:~# ldd /home/eyberg/node-v12.13.0-linux-x64/bin/node
        linux-vdso.so.1 &lt;span class="o"&gt;(&lt;/span&gt;0x00007ffeaf7e9000&lt;span class="o"&gt;)&lt;/span&gt;
        libdl.so.2 &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; /lib/x86_64-linux-gnu/libdl.so.2 &lt;span class="o"&gt;(&lt;/span&gt;0x00007fef28ab4000&lt;span class="o"&gt;)&lt;/span&gt;
        libstdc++.so.6 &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; /usr/lib/x86_64-linux-gnu/libstdc++.so.6 &lt;span class="o"&gt;(&lt;/span&gt;0x00007fef28732000&lt;span class="o"&gt;)&lt;/span&gt;
        libm.so.6 &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; /lib/x86_64-linux-gnu/libm.so.6 &lt;span class="o"&gt;(&lt;/span&gt;0x00007fef2842e000&lt;span class="o"&gt;)&lt;/span&gt;
        libgcc_s.so.1 &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; /lib/x86_64-linux-gnu/libgcc_s.so.1 &lt;span class="o"&gt;(&lt;/span&gt;0x00007fef28217000&lt;span class="o"&gt;)&lt;/span&gt;
        libpthread.so.0 &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; /lib/x86_64-linux-gnu/libpthread.so.0 &lt;span class="o"&gt;(&lt;/span&gt;0x00007fef27ffa000&lt;span class="o"&gt;)&lt;/span&gt;
        libc.so.6 &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; /lib/x86_64-linux-gnu/libc.so.6 &lt;span class="o"&gt;(&lt;/span&gt;0x00007fef27c5b000&lt;span class="o"&gt;)&lt;/span&gt;
        /lib64/ld-linux-x86-64.so.2 &lt;span class="o"&gt;(&lt;/span&gt;0x00007fef28cb8000&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What about executables?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@instance-1:~# find / &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="nt"&gt;-executable&lt;/span&gt; | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
1339
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;1300?!? We can attack 1300 programs on this fresh instance? All we did was install node. Let's try that query again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@instance-1:~# find / &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="nt"&gt;-executable&lt;/span&gt; | xargs file | &lt;span class="nb"&gt;grep &lt;/span&gt;executable | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
754
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well we drilled it down close to halfway but still 750??&lt;/p&gt;

&lt;p&gt;A heavily seccomp'd container infrastructure might prevent some of this behavior but then you are missing out on the whole serverless part of the idea and &lt;a href="https://www.techrepublic.com/article/docker-containers-are-filled-with-vulnerabilities-heres-how-the-top-1000-fared/" rel="noopener noreferrer"&gt;container security&lt;/a&gt; &lt;a href="https://snyk.io/blog/top-ten-most-popular-docker-images-each-contain-at-least-30-vulnerabilities/" rel="noopener noreferrer"&gt;does not&lt;/a&gt; &lt;a href="https://neuvector.com/docker-security/runc-docker-vulnerability/" rel="noopener noreferrer"&gt;have a great track record&lt;/a&gt;. Also, we haven't even begun to talk about why the linux kernel is +15MLOC - half of it is just drivers for hardware that doesn't exist in a virtual machine, then there's all the support for users, and IPC and scheduling and .... anyways, that's for a different blogpost.&lt;/p&gt;

&lt;p&gt;So we've now shown that merely setting up a node webserver can be a pain even when we aren't doing things like putting it into an init manager or dropping privileges or any other sane activity.&lt;/p&gt;

&lt;p&gt;Securing it becomes a whole new level of batshittery.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Serverless Unikernels&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's start fixing the problem now that we have identified it. Let's take this same node.js webserver and turn it into a unikernel using the &lt;a href="https://github.com/nanovms/nanos" rel="noopener noreferrer"&gt;Nanos&lt;/a&gt; kernel and the &lt;a href="https://github.com/nanovms/ops" rel="noopener noreferrer"&gt;OPS&lt;/a&gt; unikernel orchestrator.&lt;/p&gt;

&lt;p&gt;If it's the first time you've done this you might want to check out this &lt;a href="https://dev.to/eyberg/stateful-serverless-with-unikernels-4ma7"&gt;tutorial&lt;/a&gt; first.&lt;/p&gt;

&lt;p&gt;Before we build the image - want to see the entirety of the filesystem first? I didn't show you the filesystem in the previous example cause no one wants to sift through 20+ pages of a tree listing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;➜  sec-article  ops pkg contents node_v12.13.0
File :/node
File :/package.manifest
Dir :/sysroot
Dir :/sysroot/lib
Dir :/sysroot/lib/x86_64-linux-gnu
File :/sysroot/lib/x86_64-linux-gnu/libc.so.6
File :/sysroot/lib/x86_64-linux-gnu/libdl.so.2
File :/sysroot/lib/x86_64-linux-gnu/libgcc_s.so.1
File :/sysroot/lib/x86_64-linux-gnu/libm.so.6
File :/sysroot/lib/x86_64-linux-gnu/libnss_dns.so.2
File :/sysroot/lib/x86_64-linux-gnu/libpthread.so.0
File :/sysroot/lib/x86_64-linux-gnu/libresolv.so.2
Dir :/sysroot/lib64
File :/sysroot/lib64/ld-linux-x86-64.so.2
Dir :/sysroot/proc
File :/sysroot/proc/meminfo
Dir :/sysroot/usr
Dir :/sysroot/usr/lib
Dir :/sysroot/usr/lib/x86_64-linux-gnu
File :/sysroot/usr/lib/x86_64-linux-gnu/libstdc++.so.6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yep - that's all 20 files of it. Actually 6 of those are just directory entries.&lt;/p&gt;

&lt;p&gt;Ok, let's build the image first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;➜  sec-article  &lt;span class="nb"&gt;cat &lt;/span&gt;build.sh
&lt;span class="c"&gt;#!/bin/sh&lt;/span&gt;

&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GOOGLE_APPLICATION_CREDENTIALS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;~/gcloud.json
ops image create &lt;span class="nt"&gt;-c&lt;/span&gt; config.json &lt;span class="nt"&gt;-p&lt;/span&gt; node_v12.13.0 &lt;span class="nt"&gt;-a&lt;/span&gt; main.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;➜  sec-article  ./build.sh
&lt;span class="o"&gt;[&lt;/span&gt;node main.js]
bucket found: my-bucket
Image creation started. Monitoring operation operation-1573756133681-59752a74faae6-6944eb54-9f7ee502.
............
Operation operation-1573756133681-59752a74faae6-6944eb54-9f7ee502 completed successfully.
Image creation succeeded node-image.
gcp image &lt;span class="s1"&gt;'node-image'&lt;/span&gt; created...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can boot it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;➜  sec-article  ops instance create &lt;span class="nt"&gt;-z&lt;/span&gt; us-west1-b &lt;span class="nt"&gt;-i&lt;/span&gt; node-image
ProjectId not provided &lt;span class="k"&gt;in &lt;/span&gt;config.CloudConfig. Using my-project from default credentials.Instance creation started using image projects/my-project/global/images/node-image. Monitoring operation operation-1573756213461-59752ac110224-644f49fc-4440b475.
.....
Operation operation-1573756213461-59752ac110224-644f49fc-4440b475 completed successfully.
Instance creation succeeded node-image-1573756213.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;➜  ~  curl &lt;span class="nt"&gt;-XGET&lt;/span&gt; http://35.247.123.61/
Error: spawn ENOSYS
➜  ~  curl &lt;span class="nt"&gt;-XGET&lt;/span&gt; http://35.247.123.61/safe
dev,etc,kernel,lib,lib64,main.js,node_v12.13.0,proc,sys,usr
➜  ~  curl &lt;span class="nt"&gt;-XGET&lt;/span&gt; http://35.247.123.61/cmd&lt;span class="se"&gt;\=&lt;/span&gt;&lt;span class="s2"&gt;"touch%20tmp"&lt;/span&gt;
Error: spawn ENOSYS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So we can see we are safely not allowing any other processes to be spawned on the end machine. It isn't a matter of filtering the calls either - the system itself straight up doesn't have support for it. If you want more programs running just boot up another instance and if you want more performance out of the server look at other languages.&lt;/p&gt;

&lt;p&gt;There are other reasons why we advocating serverless unikernels like this besides security and in upcoming blogposts we'll start showing other superpowers of this style of infrastructure.&lt;/p&gt;

</description>
      <category>unikernels</category>
      <category>serverless</category>
      <category>security</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Stateful Serverless with Unikernels</title>
      <dc:creator>Ian Eyberg</dc:creator>
      <pubDate>Wed, 13 Nov 2019 21:28:27 +0000</pubDate>
      <link>https://dev.to/eyberg/stateful-serverless-with-unikernels-4ma7</link>
      <guid>https://dev.to/eyberg/stateful-serverless-with-unikernels-4ma7</guid>
      <description>&lt;p&gt;One of the big problems with cloud native computing is the reluctance to use what is called a stateful service. Think everything from databases to even just logging a web application and actually keeping those logs. To some of us it sounds a bit crazy to even distinguish between 'stateful' and 'stateless'. Unless you are on some functional programming kick you are going to deal with state.&lt;/p&gt;

&lt;p&gt;If you do choose to use traditional cloud native stateful applications you get to learn about everything from stateful sets to persistent volumes to everything else. For your average dev this is just too much craziness.&lt;/p&gt;

&lt;p&gt;It gets really crazy when you start to think that most cloud native and serverless frameworks &lt;strong&gt;&lt;em&gt;are already running&lt;/em&gt;&lt;/strong&gt; on top of public cloud environments like AWS or Google Cloud. If you haven't been told before "the cloud" is simply an API to virtualization -- and there's a non-trivial amount of software that makes that work. AWS and Google Cloud have gone out of their way to provide a truly cloud native experience by writing the underlying infrastructure to do the networking and the storage and everything else. To be clear - I'm not talking about ECS and GKE here. I'm talking about when you go and boot a VM on one of those respective systems think about what all is happening underneath.&lt;/p&gt;

&lt;p&gt;It almost makes you wonder why development teams would choose to re-write all of that knowing full well that they are going to do a crappier job than the infrastructure teams at Google/Amazon. To boot, most of the cloud native ecosystem takes serious performance hits from having to replicate networking and storage layers on top of the existing infrastructure and they suffer from serious security issues like &lt;a href="https://unit42.paloaltonetworks.com/graboid-first-ever-cryptojacking-worm-found-in-images-on-docker-hub/"&gt;cryptojacking&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I was re-watching this talk from Andrew Tanenbaum recently and he asks a very poignant question:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/hvkn0VcjVPY"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;"Why are our operating systems not like our tvs? You click the tv on and you click it off and it just works."&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Good question Andrew!&lt;/p&gt;

&lt;p&gt;The cloud native ecosystem makes some developers lives easier because they don't need to mess with configuring or deploying the base operating system but isn't there something that is kinda like a container but also has persistence and security baked in? Ever wish you could have a true cloud native serverless option but with actual real persistence?&lt;/p&gt;

&lt;p&gt;Well you can. Today we'll show you how to deploy stateful serverless with unikernels.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stateful Serverless&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For this tutorial you'll want to go grab &lt;a href="https://ops.city"&gt;OPS&lt;/a&gt;. If you'd like to build from source go check out the &lt;a href="https://github.com/nanovms/ops"&gt;github repo&lt;/a&gt;. We'll be deploying to Google Cloud today but you can also sub it out for &lt;a href="https://nanovms.com/dev/tutorials/deploying-nanos-node-js-unikernels-to-aws"&gt;AWS&lt;/a&gt;. On Google Cloud you need a bucket to store your images in and a &lt;a href="https://cloud.google.com/iam/docs/creating-managing-service-account-keys"&gt;service key&lt;/a&gt; (that I stick in ~/gcloud.json) and then create this config.json:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"CloudConfig"&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;"ProjectID"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"my-project"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Zone"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-west1-b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"BucketName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"my-bucket"&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;Now let's look at some sample code. We have a very simple node.js webserver that does two things. It either appends to a file through the '/savelog' url or it shows what's in the log via the '/getlog' url.&lt;/p&gt;

&lt;p&gt;Also - I'm not a practicing node developer so please excuse the non-idiomatic craziness of the code. Apologies in advance.&lt;/p&gt;

&lt;p&gt;I left the port configurable here so you can try it out on a different port locally before deploying it out on gcloud.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&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;fs&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;http&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;http&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;logfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bob.log&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;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createServer&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;req&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/getlog&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="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;existsSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logfile&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="s2"&gt;File not found&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="k"&gt;else&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;contents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logfile&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="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="nx"&gt;contents&lt;/span&gt;&lt;span class="p"&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;writeHead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&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;Content-Type&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;text/plain&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;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contents&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&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="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/savelog&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="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appendFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logfile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&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;writeHead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&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;Content-Type&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;text/plain&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;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;logged&lt;/span&gt;&lt;span class="se"&gt;\n&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="k"&gt;else&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;writeHead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&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;Content-Type&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;text/plain&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;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello World&lt;/span&gt;&lt;span class="se"&gt;\n&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="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.0.0.0&lt;/span&gt;&lt;span class="dl"&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;Server running at http://127.0.0.1:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;We can build our image simply by stating we want the node version 12.13.0 package and pointing it at our entryfile bob.js.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GOOGLE_APPLICATION_CREDENTIALS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;~/gcloud.json

ops image create &lt;span class="nt"&gt;-c&lt;/span&gt; config.json &lt;span class="nt"&gt;-p&lt;/span&gt; node_v12.13.0 &lt;span class="nt"&gt;-a&lt;/span&gt; bob.js
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If we run that we can build our image - it's fairly quick cause all it's doing is copying the disk image up into our bucket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;➜  stateful-serverless  ./build.sh
&lt;span class="o"&gt;[&lt;/span&gt;node bob.js]
bucket found: my-bucket
Image creation started. Monitoring operation operation-1573675163382-5973fcd1af403-c6abea13-cf898ff6.
...............
Operation operation-1573675163382-5973fcd1af403-c6abea13-cf898ff6 completed successfully.
Image creation succeeded node-image.
gcp image &lt;span class="s1"&gt;'node-image'&lt;/span&gt; created...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;One that is done we can create an instance like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt; ops instance create &lt;span class="nt"&gt;-z&lt;/span&gt; us-west1-b &lt;span class="nt"&gt;-i&lt;/span&gt; node-image
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;ProjectId not provided &lt;span class="k"&gt;in &lt;/span&gt;config.CloudConfig. Using my-project from default credentials.Instance creation started using image projects/my-project/global/images/node-image. Monitoring operation operation-1573675228678-5973fd0ff4976-a6b7132d-9aa57c18.
.....
Operation operation-1573675228678-5973fd0ff4976-a6b7132d-9aa57c18 completed successfully.
Instance creation succeeded node-image-1573675228.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Then we can hit it up a few times by querying '/savelog'. After that we can view '/getlog' to see that we saved our data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;eyberg@box:~/stateful-services&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-XGET&lt;/span&gt; http://34.83.134.230/savelog
logged
eyberg@box:~/stateful-services&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-XGET&lt;/span&gt; http://34.83.134.230/savelog
logged
eyberg@box:~/stateful-services&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-XGET&lt;/span&gt; http://34.83.134.230/savelog
logged
eyberg@box:~/stateful-services&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-XGET&lt;/span&gt; http://34.83.134.230/getlog
&lt;span class="nb"&gt;test
test
test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Now let's stop the instance - not terminate it but stop it. This is where traditional cloud native solutions and other serverless based options tend to have problems. They view the running image as ephemeral and not ever coming back. However for true cloud native applications like unikernels this is simply not the case. All we are doing is telling Google to merely stop the instance not kill it. This is good if you want say a &lt;em&gt;cough&lt;/em&gt; cloud native database &lt;em&gt;cough&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;We can go ahead and stop the instance and then restart it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;eyberg@box:~/stateful-services&lt;span class="nv"&gt;$ &lt;/span&gt;ops instance stop &lt;span class="nt"&gt;-p&lt;/span&gt; my-project &lt;span class="nt"&gt;-z&lt;/span&gt; us-west1-b node-image-1573676028
Instance stopping started. Monitoring operation operation-1573676572018-5974021110877-37b0f038-95aa93d0.
.............................................................
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;eyberg@box:~/stateful-services&lt;span class="nv"&gt;$ &lt;/span&gt;ops instance list &lt;span class="nt"&gt;-p&lt;/span&gt; my-project &lt;span class="nt"&gt;-z&lt;/span&gt; us-west1-b
+----------------------------------+------------+-------------------------------+-------------+---------------+
|               NAME               |   STATUS   |            CREATED            | PRIVATE IPS |  PUBLIC IPS   |
+----------------------------------+------------+-------------------------------+-------------+---------------+
| node-image-1573676028            | TERMINATED | 2019-11-13T12:13:50.106-08:00 | 10.240.0.50 |               |
+----------------------------------+------------+-------------------------------+-------------+---------------+
| releases-150719-14-07-1563179925 | TERMINATED | 2019-07-15T01:38:47.354-07:00 | 10.240.0.9  | 35.233.232.59 |
+----------------------------------+------------+-------------------------------+-------------+---------------+
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;eyberg@box:~/stateful-services&lt;span class="nv"&gt;$ &lt;/span&gt;ops instance start &lt;span class="nt"&gt;-p&lt;/span&gt; my-project &lt;span class="nt"&gt;-z&lt;/span&gt; us-west1-b node-image-1573676028
Instance started. Monitoring operation operation-1573676869968-5974032d363e0-d0a18a2c-0d56186d.
...
Operation operation-1573676869968-5974032d363e0-d0a18a2c-0d56186d completed successfully.
Instance started node-image-1573676028.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;





&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;eyberg@box:~/stateful-services&lt;span class="nv"&gt;$ &lt;/span&gt;ops instance list &lt;span class="nt"&gt;-p&lt;/span&gt; my-project &lt;span class="nt"&gt;-z&lt;/span&gt; us-west1-b
+----------------------------------+------------+-------------------------------+-------------+---------------+
|               NAME               |   STATUS   |            CREATED            | PRIVATE IPS |  PUBLIC IPS   |
+----------------------------------+------------+-------------------------------+-------------+---------------+
| node-image-1573676028            | RUNNING    | 2019-11-13T12:13:50.106-08:00 | 10.240.0.50 | 34.82.108.88  |
+----------------------------------+------------+-------------------------------+-------------+---------------+
| releases-150719-14-07-1563179925 | TERMINATED | 2019-07-15T01:38:47.354-07:00 | 10.240.0.9  | 35.233.232.59 |
+----------------------------------+------------+-------------------------------+-------------+---------------+
eyberg@box:~/stateful-services&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-XGET&lt;/span&gt; http://34.82.108.88/getlog
&lt;span class="nb"&gt;test
test
test
test
test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Hooray - your data is still there. This is precisely what you would see in a traditional linux environment yet now you get the benefit of the container/serverless like atmosphere as well.&lt;/p&gt;

&lt;p&gt;You'll notice that the ip might change as we don't have an elastic/static ip attached to it - that's ok and expected. If you were actually deploying to prod you might assign one or stick this behind a load balancer instead.&lt;/p&gt;

&lt;p&gt;If you're new to this style of infrastructure it's worth pointing out a few things. These instances are not linux instances - as in they don't even have a linux kernel inside - at all. They don't have the concept of usernames/passwords. You can't login to them and they can not run more than one program per instance. This is all by design to make them faster, more secure and easier to manage then existing options.&lt;/p&gt;

&lt;p&gt;This was just a quick and dirty example to show you the basic concepts behind the idea of stateful serverless. No servers to deal with but you can keep your state cake and eat it too.&lt;/p&gt;

&lt;p&gt;What will you deploy?&lt;/p&gt;

</description>
      <category>unikernels</category>
      <category>serverless</category>
      <category>javascript</category>
      <category>devops</category>
    </item>
    <item>
      <title>OPS - Rethinking Cloud Infrastructure</title>
      <dc:creator>Ian Eyberg</dc:creator>
      <pubDate>Tue, 22 Jan 2019 21:46:39 +0000</pubDate>
      <link>https://dev.to/eyberg/ops---rethinking-cloud-infrastructure-c40</link>
      <guid>https://dev.to/eyberg/ops---rethinking-cloud-infrastructure-c40</guid>
      <description>&lt;p&gt;Unikernels have been a technology that have been predicted for the past few years to be the future of software infrastructure.&lt;/p&gt;

&lt;p&gt;Wait - hold up - what's a unikernel!? One way to think of a unikernel is what would happen if you converted your application into it's own operating system. It is a single purpose operating system versus a general purpose one like Linux. Another really large difference is that a unikernel is meant to only run one application per server. Since practically everything that is deployed to day is deployed onto virtual machines (eg: &lt;em&gt;all&lt;/em&gt; of public cloud) unikernels propose that you don't need a full blown operating system for each application you want to deploy. This makes them run faster, more secure and they become way tinier.&lt;/p&gt;

&lt;p&gt;However, unikernels have not been without their problems. One of these problems that unikernels have traditionally suffered from is that to truly utilize them in the past you had to have been a systems developer or a low level c programmer.&lt;/p&gt;

&lt;p&gt;Not anymore.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ops.city" rel="noopener noreferrer"&gt;OPS&lt;/a&gt; is a tool to help anyone build and run unikernels in whatever language or as whatever application they want. OPS simply loads ELF binaries just like Linux does but with unikernel benefits. Before 'unikernels' we had what were known as 'library operating systems'. OPS and the kernel that it uses 'Nanos' embraces that concept. They are both under heavy active development and have a full time team of paid kernel engineers working on it. (This has also been historically a big problem for the ecosystem.)&lt;/p&gt;

&lt;p&gt;For local development OPS works on linux or mac currently.&lt;/p&gt;

&lt;p&gt;To get started you'll want to install the cli tool - if you aren't a Go user you can just download the binary from the site but if you are a Go user than you can build it yourself as well once you obtain the &lt;a href="https://github.com/nanovms/ops" rel="noopener noreferrer"&gt;source&lt;/a&gt; .&lt;/p&gt;

&lt;h3&gt;
  
  
  Install
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl https://ops.city/get.sh -sSfL | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Let's try out a quick Node.js hello world.&lt;/p&gt;

&lt;p&gt;Put this into a file:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  console.log("Hello World!");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then you can run it.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  $ ops load node_v11.15.0 -a ex.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Easy huh? Want to try something a bit more complicated? Let's try a Go webserver.&lt;/p&gt;

&lt;p&gt;Put this into main.go:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  package main

  import (
      "log"
      "net/http"
  )

  func main() {
      fs := http.FileServer(http.Dir("static"))
      http.Handle("/", fs)

      log.Println("Listening...on 8080")
      http.ListenAndServe(":8080", nil)
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If you are on a mac specify the os as 'linux'. It's important to note that it's not running linux underneath - at all. Unikernels aren't containers. The only reason we specify GOOS as linux is that Nanos implements the POSIX standard (to some degree) and loads our programs as ELFs.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  $ GOOS=linux go build main.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You can verify this by looking at the filetype:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜  twe  file main
main: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Note that I built this on a mac whose native file type is a Mach-O. We mainly support ELF since that is what is deployed to production.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜  twe  file main
main: Mach-O 64-bit executable x86_64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now let's create a folder to put our static assets in:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  $ mkdir static
  $ cd static
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  &amp;lt;!doctype html&amp;gt;
  &amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;
  &amp;lt;meta charset="utf-8"&amp;gt;
      &amp;lt;title&amp;gt;A static page&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
      &amp;lt;h1&amp;gt;Hello from a static page&amp;lt;/h1&amp;gt;
  &amp;lt;/body&amp;gt;
  &amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Then let's specify a config.json for this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; {
      "Dirs" : ["static"]
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This simply states that we wish to put the directory 'static' into /static as a folder in our filesystem. There are many advanced options that you can explore with OPS config.json but please be aware that it is under active development and a lot of it can and will change.&lt;/p&gt;

&lt;p&gt;Now we are going to run it. OPS implements a very small wrapper around QEMU. Qemu allows us to run the resulting built image as a virtual machine. The virtual machine is the output of ops building your app as it's own unique little operating system. Have you ever wondered to build your own operating system? Well you just did.&lt;/p&gt;

&lt;p&gt;By default OPS will run your unikernel in what's known as 'user-mode' networking without KVM acceleration. Usermode networking is a bit slower than a proper bridged network (what you'd get on AWS or GCE) but we are playing around on your laptop so this is easy to get started with. Also, keep in mind that we run this as a non-root user by default and by default KVM will want root privileges. As a more advanced lesson later we'll show you how to configure that properly. Also keep in mind that on mac we don't have access to KVM, however, there is an equivalent - Intel HAX which we can use if we want.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  $ ops run -p 8080 -c config.json server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now that it is running you should be able to run curl against your server and wah-lah!&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  curl http://localhost:8080/hello.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You've just built and ran your first Go webserver as a unikernel! How cool is that?&lt;/p&gt;

&lt;p&gt;Check out the github repo, star it and let's see what else can be created!&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/nanovms" rel="noopener noreferrer"&gt;
        nanovms
      &lt;/a&gt; / &lt;a href="https://github.com/nanovms/ops" rel="noopener noreferrer"&gt;
        ops
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      ops - build and run nanos unikernels
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;OPS&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href="https://circleci.com/gh/nanovms/ops" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/b21a6bcf56c781b29393d2a3b1ade0ad4dda78fee399acb90e8dcbdf26b69b2e/68747470733a2f2f636972636c6563692e636f6d2f67682f6e616e6f766d732f6f70732e7376673f7374796c653d737667" alt="CircleCI"&gt;&lt;/a&gt;
&lt;a href="https://goreportcard.com/report/github.com/nanovms/ops" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/8f76bf9ed1993e9c8cf073869f234dbd0d6a0068393bdee6baf0c18fbc295e8a/68747470733a2f2f676f7265706f7274636172642e636f6d2f62616467652f6769746875622e636f6d2f6e616e6f766d732f6f7073" alt="Go Report"&gt;&lt;/a&gt;
&lt;a href="http://godoc.org/github.com/nanovms/ops" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/f20f5ec2d43422e238a2d4cf1142d61ab92afaea992d0d1415ef14945200975f/68747470733a2f2f676f646f632e6f72672f6769746875622e636f6d2f6e616e6f766d732f6f70733f7374617475732e737667" alt="Go Docs"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;
  &lt;a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/5abd2935427a644e37f66c2f368beb107c5400cef558a92149a1d06924a88684/68747470733a2f2f692e696d6775722e636f6d2f4f7466414142552e706e67"&gt;&lt;img src="https://camo.githubusercontent.com/5abd2935427a644e37f66c2f368beb107c5400cef558a92149a1d06924a88684/68747470733a2f2f692e696d6775722e636f6d2f4f7466414142552e706e67"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;Ops is a tool for creating and running a &lt;a href="https://github.com/nanovms/nanos" rel="noopener noreferrer"&gt;Nanos&lt;/a&gt; unikernel. It is used to
package, create, and run your application as a &lt;a href="https://github.com/nanovms/nanos" rel="noopener noreferrer"&gt;nanos&lt;/a&gt; unikernel instance.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/nanovms/ops#installation" rel="noopener noreferrer"&gt;Installation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/nanovms/ops#hello-world" rel="noopener noreferrer"&gt;Hello World&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/nanovms/ops#cloud" rel="noopener noreferrer"&gt;Cloud&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/nanovms/ops#support" rel="noopener noreferrer"&gt;Support&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Check out the &lt;a href="https://nanovms.gitbook.io/ops/" rel="nofollow noopener noreferrer"&gt;DOCS&lt;/a&gt;.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Installation&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;Most users should just download the binary from the website:&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Binary install&lt;/h2&gt;
&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;curl https://ops.city/get.sh -sSfL &lt;span class="pl-k"&gt;|&lt;/span&gt; sh&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;If you don't like this option you can also download pre-made packages
for various systems &lt;a href="https://ops.city/downloads" rel="nofollow noopener noreferrer"&gt;here&lt;/a&gt; and you can also
build from source.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Desktop applications&lt;/h2&gt;

&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operating System&lt;/th&gt;
&lt;th&gt;Download&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;macOS&lt;/td&gt;
&lt;td&gt;&lt;a href="https://storage.googleapis.com/cli/darwin/ops.pkg" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Get it on macOS" width="134px" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnanovms%2Fops%2Fmaster%2Fassets%2FBadgeMacOS.png"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;td&gt;&lt;a href="https://storage.googleapis.com/cli/windows/ops-desktop-setup-win-x64.exe" rel="nofollow noopener noreferrer"&gt;&lt;img alt="Get it on Windows" width="134px" src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fnanovms%2Fops%2Fmaster%2Fassets%2Fbadgewindows.png"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;MacOS via Homebrew&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Add the repo &amp;amp; install:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;brew tap nanovms/homebrew-ops&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;brew install nanovms/ops/ops&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;See the &lt;a href="https://github.com/nanovms/homebrew-ops/blob/master/Formula/ops.rb" rel="noopener noreferrer"&gt;formula file&lt;/a&gt; for details.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Debian / Redhat:&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Add a deb src:&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;sudo vi /etc/apt/sources.list.d/fury.list
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;deb [trusted=yes] https://apt.fury.io/nanovms/ /
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Update your sources &amp;amp;&amp;amp; install:&lt;/p&gt;
&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;sudo apt-get update &amp;amp;&amp;amp; sudo apt-get install ops
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Build and Install from source&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Building from source is easy if you have used Go before.&lt;/p&gt;
&lt;p&gt;This program…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/nanovms/ops" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


</description>
      <category>unikernels</category>
      <category>serverless</category>
      <category>containers</category>
      <category>virtualization</category>
    </item>
  </channel>
</rss>
