<?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: Andri</title>
    <description>The latest articles on DEV Community by Andri (@andrioid).</description>
    <link>https://dev.to/andrioid</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%2F36872%2Fd572614d-04d7-49a6-bec7-7cf07c8a9a60.jpg</url>
      <title>DEV Community: Andri</title>
      <link>https://dev.to/andrioid</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/andrioid"/>
    <language>en</language>
    <item>
      <title>Tech Podcasts for 2024</title>
      <dc:creator>Andri</dc:creator>
      <pubDate>Wed, 14 Feb 2024 08:04:27 +0000</pubDate>
      <link>https://dev.to/andrioid/tech-podcasts-for-2024-236b</link>
      <guid>https://dev.to/andrioid/tech-podcasts-for-2024-236b</guid>
      <description>&lt;p&gt;Some of the podcasts I enjoy during my bike-trip to work.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://softskills.audio/"&gt;Soft Skills Engineering&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Soft Skills are an underrated ability in engineers and Dave and Jamison deliver their advise in such a funny and helpful way that it's hard not to smile while listening to it.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://syntax.fm/"&gt;Syntax.fm&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Scott and Wes are really in touch with what's trending in the tech world and usually have both great guests and interesting takes on a various range of subjects.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://changelog.com/gotime"&gt;Go time&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;A great Go(lang) specific show with deep technical topics and plenty of humor.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://changelog.com/podcast"&gt;The Changelog&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Good all-around tech podcast.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.dotnetrocks.com/about"&gt;.NET Rocks&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Even though I don't really program in .NET, I enjoy this show very much. Patterns and ways of working are universal and the hosts are very entertaining.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://darknetdiaries.com/"&gt;Darknet Diaries&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Host interviews hackers and other cyber-criminals about their exploits and mistakes.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.devtools.fm/"&gt;devtools.fm&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Recently started listening, but so far very happy with their content which focuses on developer tooling (duh).&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://hasspodcast.io/"&gt;Home Assistant Podcast&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;As someone who likes tinkering with smart-home stuff, this show is very inspiring to what you can do with a few lines of Python or YAML.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://changelog.com/jsparty"&gt;JS Party&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;In depth show about all things JavaScript/TypeScript.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://reactnativeradio.com/"&gt;React Native Radio&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;A show from Infinite Red about React Native, Expo and the art of making cross platform apps.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://selfhosted.show/"&gt;Self Hosted&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;An inspiring show about software that you can host yourself without "the cloud". I may not do a lot of self hosting, but I enjoy listening to others talk about it in detail.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://shoptalkshow.com/"&gt;Shop Talk&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Somewhat Ruby specific, but a great all-around tech pod cast.&lt;/p&gt;

</description>
      <category>tech</category>
      <category>podcast</category>
    </item>
    <item>
      <title>ENV files</title>
      <dc:creator>Andri</dc:creator>
      <pubDate>Sat, 18 Dec 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/andrioid/env-files-3j8m</link>
      <guid>https://dev.to/andrioid/env-files-3j8m</guid>
      <description>&lt;p&gt;An environmental-variable is a way for the environment (your operating-system, or shell) to share information with future processes. Some ENV vars are global (and set by the OS), others are only useful in a certain context.&lt;/p&gt;

&lt;p&gt;You could be using a configuration file, but the world of hosting has very much adopted the &lt;a href="https://12factor.net"&gt;12 Factor App&lt;/a&gt; way of configuring applications. For environments like CI/CD, Heroku and Kubernetes, this makes a lot of sense.&lt;/p&gt;

&lt;p&gt;In development though, this can be quite awkard to use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Backend API
&lt;/h3&gt;

&lt;p&gt;You've written a beautiful monolith, but it needs a couple of things to run properly. It uses a &lt;strong&gt;private key&lt;/strong&gt; to sign auth tokens, and it uses &lt;strong&gt;AWS credentials&lt;/strong&gt; for uploads to S3.&lt;/p&gt;

&lt;p&gt;You decide to use ENV variables and decide on &lt;code&gt;AUTH_KEY&lt;/code&gt; and &lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt; and &lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt;. Then you set up your hosting, and configure your app to abort if any of those are missing.&lt;/p&gt;

&lt;p&gt;In development though, instead of running &lt;code&gt;npm run dev&lt;/code&gt;, you now need to run &lt;code&gt;AUTH_KEY=xxx AWS_ACCESS_KEY_ID=xxx AWS_SECRET_ACCESS_KEY=xxx npm run dev&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is rather annoying, and might prompt you to write a blog post about ENV variables...&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating the .env file
&lt;/h3&gt;

&lt;p&gt;Instead of defining the ENV variables every time, we can create a &lt;code&gt;.env&lt;/code&gt; file in our project workspace. It usually looks something like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# JWT encoding key
AUTH_KEY=youWillNeverGuessThisYouEvilHackers
# AWS Developer Access
AWS_ACCESS_KEY_ID=xxx
AWS_SECRET_ACCESS_KEY=xxx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You may be tempted to check this file into source control (e.g. Git) and store with your code. &lt;strong&gt;That would be a mistake&lt;/strong&gt;. Especially if you decide at a later point to open-source your project. Then you'd also be giving everyone access to your AWS credentials. Even if you later delete the file from your repo.&lt;/p&gt;

&lt;p&gt;Instead, add it to your &lt;code&gt;.gitignore&lt;/code&gt; file to make sure that it will &lt;em&gt;never&lt;/em&gt; get accidentally pushed with your code.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;It may not be 100% secure, to store stuff like this on your developer laptop, but it's still better than storing secrets in your repository.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Pro tip: New developers on your projects may not know anything about ENV variables or your local &lt;code&gt;.env&lt;/code&gt; file. So make sure you update your documentation, and provide useful error messages if those ENV variables are missing.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Never create &lt;code&gt;.env&lt;/code&gt; files for anything other than your development setup. It is very easy to accidentally expose secrets that way.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Reading the .env file
&lt;/h3&gt;

&lt;p&gt;If we now run our backend, it will complain that our ENV variables are not set. We have to tell Node (or whatever) about those variables.&lt;/p&gt;

&lt;p&gt;On Linux/Mac this is quite easy.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pass the env-vars to MYCOMMAND
&lt;/h4&gt;

&lt;p&gt;In this case &lt;code&gt;npm run dev&lt;/code&gt; will have access to any ENV variables in the file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eval $(egrep -v '^#' .env | xargs) npm run dev

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Export the vars in .env into your shell
&lt;/h4&gt;

&lt;p&gt;It's also possible to "export" the variables to your current session. That way, any command you run afterwards from the same shell will inherit it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export $(egrep -v '^#' .env | xargs)
npm run build
npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Node.js
&lt;/h3&gt;

&lt;p&gt;It is also possible to read ENV files without doing shell-script dark arts.&lt;/p&gt;

&lt;h4&gt;
  
  
  dotenv
&lt;/h4&gt;

&lt;p&gt;You can inject the ENV variables into your Node.js process like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install --save-dev dotenv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In your app.js&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;require('dotenv').config()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my experience, this is a horrible way of reading the ENV file. Remember, that this is a convenience for development. Why are we adding it to production code?&lt;/p&gt;

&lt;h4&gt;
  
  
  dotenv, without polluting your code
&lt;/h4&gt;

&lt;p&gt;Add a file called &lt;code&gt;env.js&lt;/code&gt; to your project workspace.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const { execSync } = require("child_process");

// This reads the .env file into the environment
// and shares it with any child process
require("dotenv").config();

const [argnode, argcmd, ...argrest] = process.argv;

// Run whatever follows `node env.js` as a child process and inherit stdin/stdout/etc
execSync(argrest.join(" "), {
  stdio: "inherit",
});

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in your package.json. In my example, I'm loading &lt;code&gt;.env&lt;/code&gt; and then running the Remix dev-server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "name": "your package"
  "scripts": {
    "dev": "node ./dev.js npm run dev:server",
    "dev:server": "remix dev"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are packages on NPM that do this for you, like &lt;a href="https://www.npmjs.com/package/dotenv-cli"&gt;dotenv-cli&lt;/a&gt; or &lt;a href="https://www.npmjs.com/package/dotenv-run-script"&gt;dotenv-run-script&lt;/a&gt;. But why install a dependency for 4 lines of code?&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>devops</category>
      <category>shell</category>
    </item>
    <item>
      <title>Host Static Websites on a VPS with TLS</title>
      <dc:creator>Andri</dc:creator>
      <pubDate>Sun, 02 May 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/andrioid/host-static-websites-on-a-vps-with-tls-m24</link>
      <guid>https://dev.to/andrioid/host-static-websites-on-a-vps-with-tls-m24</guid>
      <description>&lt;p&gt;Hosting doesn't have to be complicated or expensive. I'll show you how to host a static site for $6/mo on DigitalOcean (&lt;a href="https://m.do.co/c/eb735821ebb8"&gt;ref link&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Be warned, this is a bit technical and you will need to type some things into a terminal. I will explain what each line does on the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the server
&lt;/h2&gt;

&lt;p&gt;Log on to the DigitalOcean &lt;a href="https://cloud.digitalocean.com"&gt;cloud console&lt;/a&gt; and create a new Droplet.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Distribution&lt;/strong&gt; : Ubuntu LTS or Debian is fine&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plan&lt;/strong&gt; : Basic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Options&lt;/strong&gt; : AMD, $6/mo (1G ram, 20G SSD, 1TB bandwidth)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Datacenter&lt;/strong&gt; : Just pick the one closest to you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication&lt;/strong&gt; : Pick a password or use a public SSH key (i use SSH keys).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hostname&lt;/strong&gt; : Something that helps you remember. Mine is called "hostingdemo"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DigitalOcean offers automatic backups for $9.60/mo and you can select that if you want, but in our case... we'll be building the site elsewhere and uploading it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TIpbrAkT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/blog/2021/do-hosting1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TIpbrAkT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/blog/2021/do-hosting1.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After a few seconds, you should see your server ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  Logging on to the server
&lt;/h2&gt;

&lt;p&gt;Note the &lt;strong&gt;IP address&lt;/strong&gt; field. Click it to copy the address. You need SSH. It should already be installed on your Linux, Mac or Windows machine.&lt;/p&gt;

&lt;p&gt;In your terminal shell (open terminal og cmd).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh root@your-droplet-ip-address
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you selected a password, you'll be prompted for that. If you selected an SSH key and that key is setup on your machine you're all logged in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing what we need
&lt;/h2&gt;

&lt;p&gt;We'll be using &lt;a href="https://caddyserver.com"&gt;Caddy&lt;/a&gt; for our web-server to use its' automatic TLS feature. Other options could be nginx or apache.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Install the web server
apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo apt-key add -
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee -a /etc/apt/sources.list.d/caddy-stable.list
apt update
apt install caddy
# Update your server, highly recommended
apt dist-upgrade -y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now open a browser and access &lt;em&gt;&lt;a href="http://your-droplet-ip-address"&gt;http://your-droplet-ip-address&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CVlLZMAJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/blog/2021/do-hosting2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CVlLZMAJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/blog/2021/do-hosting2.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That was easy, right?&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding a web user to upload with
&lt;/h2&gt;

&lt;p&gt;So far, we've been using "root", a system user that can do anything. We don't want to spread that around. Create a new user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;useradd webuser --password somethingstronger -s /sbin/nologin -m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates "webuser" with the password "somethingstronger" (and I do mean, it... use your own strong password), disables the SSH login and creates a home directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enabling SFTP
&lt;/h2&gt;

&lt;p&gt;To be able to upload files from a non-privleged user we need to change the SSH configuration a bit. Open the SSH daemon config in "/etc/ssh/sshd_config".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nano /etc/ssh/sshd_config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure that the following lines look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Subsystem sftp internal-sftp
PasswordAuthentication yes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first one enables use of SFTP without having a login shell (like our limited webuser) and the second allows password logins for SSH.&lt;/p&gt;

&lt;p&gt;Restart the SSH server&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;service ssh restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test the login from your machine (this works on Mac and Linux, unsure about Windows)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sftp webuser@your-droplet-ip

webuser@your-droplet-ip's password: 
Connected to your-droplet-ip.
sftp&amp;gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Tell Caddy to serve files from our webuser directory
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nano /etc/caddy/Caddyfile

:80

# Set this path to your site's directory.
root * /home/webuser

# Enable the static file server.
file_server

# Refer to the Caddy docs for more information:
# https://caddyserver.com/docs/caddyfile

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells Caddy to serve from the webuser home directory instead. But, we need to give it access.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;usermod -a -G webuser caddy
service caddy restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Test the web-server
&lt;/h3&gt;

&lt;p&gt;Upload an "index.html" to your server. Use whatever SFTP client you like (like FileZilla or winscp). From my local machine, I create a simple HTML file on my computer called "index.html" with "hello world" in it. Then I uploaded it to our server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sftp sftp webuser@your-droplet-ip
put index.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7V_S-1JG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/blog/2021/do-hosting3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7V_S-1JG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/blog/2021/do-hosting3.png" alt="hello world on caddy"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding a hostname or a domain
&lt;/h2&gt;

&lt;p&gt;I added a DNS record to give my server a name. How exactly you add this depends on your DNS hosting provider. DigitalOcean has this under "Networking -&amp;gt; Domains".&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hostname&lt;/strong&gt; : whateveryouwant (I used "hostingdemo")&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Record Type&lt;/strong&gt; : A&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Target&lt;/strong&gt; : your-droplet-ip-address&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--HkETabak--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/blog/2021/do-hosting4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--HkETabak--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/blog/2021/do-hosting4.png" alt="DNS record"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, if I point a browser to &lt;a href="http://hostingdemo.andri.dk"&gt;http://hostingdemo.andri.dk&lt;/a&gt;...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hkSS_26z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/blog/2021/do-hosting5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hkSS_26z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/blog/2021/do-hosting5.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Encryption (TLS/SSL)
&lt;/h2&gt;

&lt;p&gt;To do this, we open our Caddyfile again in "/etc/caddy/Caddyfile" and change it to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hostingdemo.andri.dk # replace this with your hostname

# Set this path to your site's directory.
root * /home/webuser

# Enable the static file server.
file_server

# Refer to the Caddy docs for more information:
# https://caddyserver.com/docs/caddyfile

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reload the configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;service caddy reload
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in your browser change the URL from "http" to "https" and reload.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--WO11PKXk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/blog/2021/do-hosting6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--WO11PKXk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/blog/2021/do-hosting6.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;We created a new server on DigitalOcean&lt;/li&gt;
&lt;li&gt;We added a new non-privileged user "webuser" and configured it to upload the site&lt;/li&gt;
&lt;li&gt;We setup a DNS name for our new site&lt;/li&gt;
&lt;li&gt;We setup Caddy with automatic TLS using &lt;a href="https://letsencrypt.org"&gt;Lets Encrypt&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Bonus
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Automatically upload your Jamstack site with Github Actions
&lt;/h3&gt;

&lt;p&gt;This is covered in more details in my &lt;a href="https://dev.to/andrioid/deploy-static-websites-anywhere-mai-temp-slug-8534104"&gt;Deploy your Static Site Anywhere&lt;/a&gt; post, but copy paste the following into a &lt;code&gt;.github/workflows/deploy.yaml&lt;/code&gt; file in your repo on Github.&lt;/p&gt;

&lt;p&gt;You need the following secrets set up on your repo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SFTP_HOST&lt;/strong&gt; : host or ip of droplet&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SFTP_USER&lt;/strong&gt; : webuser&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SFTP_PASS&lt;/strong&gt; : yourverysecretpassword
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: buildpack
on: [push, pull_request]
jobs:
  remote-build:
    runs-on: ubuntu-latest
    container:
      image: docker:stable
      volumes:
        - /home/runner:/workspace
    env:
      IMG_NAME: ${{ github.workflow }}
      NODE_ENV: production # Makes this a little more clean on the Node side
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Login to GitHub Container Registry
        uses: docker/login-action@v1
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # Downloads and caches the pack command (the code is open)
      - uses: andrioid/setup-pack@main
      # Attempt to fetch any previous image so that we may use it for caching
      - run: docker pull ghcr.io/${GITHUB_REPOSITORY}/${IMG_NAME} || true
      # Builds your site with buildpacks (heroku builder)
      - run: pack build ghcr.io/${GITHUB_REPOSITORY}/${IMG_NAME} --builder=heroku/buildpacks:20
      # Pushes your new image to Github's Container Registry
      - run: docker push ghcr.io/${GITHUB_REPOSITORY}/${IMG_NAME}
      # Copy the build assets from the container and cleanup
      - run: CONTAINER_ID=$(docker create ghcr.io/${GITHUB_REPOSITORY}/${IMG_NAME}:latest /bin/sh) &amp;amp;&amp;amp; docker cp ${CONTAINER_ID}:/workspace/public . &amp;amp;&amp;amp; docker rm ${CONTAINER_ID}
      - run: ls -alh public
      - uses: actions/upload-artifact@v2
        with:
          name: public
          path: public/

  deploy-via-sftp:
    needs: remote-build
    runs-on: ubuntu-latest
    env:
      RCLONE_CONFIG_VPS_TYPE: sftp
      RCLONE_CONFIG_VPS_HOST: ${{ secrets.SFTP_HOST }}
      RCLONE_CONFIG_VPS_USER: ${{ secrets.SFTP_USER }}
      RCLONE_CONFIG_VPS_PASS_PLAIN: ${{ secrets.SFTP_PASS }}
    steps:
      - uses: andrioid/setup-rclone-action@main
      - uses: actions/download-artifact@v2
        with:
          name: public
          path: public
      - run: echo "RCLONE_CONFIG_VPS_PASS=$(rclone obscure ${RCLONE_CONFIG_VPS_PASS_PLAIN})" &amp;gt;&amp;gt; $GITHUB_ENV
      - run: rclone -v copy public vps:.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This requires you to have a repository on Github and that your site can be built with Buildpacks. I have this running with Gatsby, but it should work with almost anything that builds into &lt;code&gt;public/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--79CnGIIU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/blog/2021/do-hosting7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--79CnGIIU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/blog/2021/do-hosting7.png" alt="Deployed via Github Actions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now my Jamstack Example site is deployed on our VPS!&lt;/p&gt;

</description>
      <category>hosting</category>
      <category>jamstack</category>
      <category>devops</category>
    </item>
    <item>
      <title>Deploy Static Websites Anywhere</title>
      <dc:creator>Andri</dc:creator>
      <pubDate>Sat, 01 May 2021 00:00:00 +0000</pubDate>
      <link>https://dev.to/andrioid/deploy-static-websites-anywhere-5dn8</link>
      <guid>https://dev.to/andrioid/deploy-static-websites-anywhere-5dn8</guid>
      <description>&lt;p&gt;Before I started my carrier in Software Development, I was a System- and Network Administrator. I used to setup web servers and managed my own little server room. I truly, do not miss upgrading operating-systems on live services or dealing with hardware failure. However, hosting web-pages back then was much easier. You'd get an FTP/SFTP access and you'd upload your files. Then they got served statically...&lt;/p&gt;

&lt;p&gt;Today, everything is about &lt;strong&gt;scale&lt;/strong&gt; and maybe prematurely so. If you want to host a typical static site today, many hosting providers force you to add a Github (or similar) integration to your repository. To be fair; these providers offer automatic TLS, CDN, fail over and full scaling. But, what if I want to upload my own files? What if I already have a build-pipeline somewhere? Then this forced build-process is not convenient, but quite &lt;strong&gt;annoying&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I'm going to show you how to break free and deploy your static site wherever you want, using nothing but Github Actions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6PeR0zWv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/blog/2021/deploy-anywhere2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6PeR0zWv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/blog/2021/deploy-anywhere2.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Our toolbox for today
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://docs.github.com/en/actions"&gt;Github Actions&lt;/a&gt;&lt;/strong&gt;: Github's build pipeline. This can be done on any CI platform. I simply chose Github actions because almost everyone uses Github and it's free for open source projects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://rclone.org"&gt;rclone&lt;/a&gt;&lt;/strong&gt;: A wonderful file-syncing tool that supports a whole range of services.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://buildpacks.io"&gt;Buildpacks&lt;/a&gt;&lt;/strong&gt;: Build almost any project without writing a single Dockerfile. It detects your project, handles caching and has been used in production at Heroku for a very long time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You also need &lt;a href="https://nodejs.org"&gt;node.js&lt;/a&gt; and &lt;a href="https://github.com/git-guides/install-git"&gt;git&lt;/a&gt; installed before we proceed. This guide expects that you know how to work with Git and Github.&lt;/p&gt;

&lt;h2&gt;
  
  
  TLDR;
&lt;/h2&gt;

&lt;p&gt;If you're anything like me, you just want to skip the bullshit and &lt;a href="https://github.com/andrioid/jamstack-example"&gt;dive straight into the code&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up our sample Gatsby site
&lt;/h2&gt;

&lt;p&gt;This could be any type of static site. I decided to go with Gatsby, because that's what I'm using. The only thing we assume here, is that &lt;code&gt;npm run build&lt;/code&gt; works and builds to &lt;code&gt;public/&lt;/code&gt;. Otherwise you have to adjust for it yourself.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; : If you already have a site you want to deploy, you can skip this part.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm init gatsby
# follow the prompts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aRfUmQPU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/blog/2021/gatsby-example-init.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aRfUmQPU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/blog/2021/gatsby-example-init.png" alt="gatsby install prompt"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; : If you enabled the feed or sitemap plugins you need to add a &lt;code&gt;metadata.siteUrl&lt;/code&gt; to your &lt;code&gt;gatsby-config.js&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd jamstack-example # or whatever you named yours
npm run develop # if you want to play with it
npm run build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you have a site in &lt;code&gt;public/&lt;/code&gt; that is ready to be deployed. Let's try it out real quick before going on to the Github part.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm run serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then direct your browser to &lt;a href="http://localhost:9000"&gt;http://localhost:9000&lt;/a&gt;. If everything went well, you should see this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--svb250qT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/blog/2021/gatsby-localhost.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--svb250qT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/blog/2021/gatsby-localhost.png" alt="gatsby on localhost"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Building it with Actions
&lt;/h2&gt;

&lt;p&gt;Log on to Github and create a new repo. On that repo, find the repository URL and copy it where it says &lt;code&gt;&amp;lt;repo&amp;gt;&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git remote add origin &amp;lt;repo&amp;gt;
git branch -M main
git push -u origin main

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now then. Let's add some actions! Create a file with this name and add it to your project.&lt;/p&gt;

&lt;h4&gt;
  
  
   &lt;strong&gt;.github/workflows/deploy-anywhere.yml&lt;/strong&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: buildpack
on: [push, pull_request]
jobs:
  remote-build:
    runs-on: ubuntu-latest
    container:
      image: docker:stable
      volumes:
        - /home/runner:/workspace
    env:
      IMG_NAME: ${{ github.workflow }}
      NODE_ENV: production # Makes this a little more clean on the Node side
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Login to GitHub Container Registry
        uses: docker/login-action@v1
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      # Downloads and caches the pack command (the code is open)
      - uses: andrioid/setup-pack@main
      # Attempt to fetch any previous image so that we may use it for caching
      - run: docker pull ghcr.io/${GITHUB_REPOSITORY}/${IMG_NAME} || true
      # Builds your site with buildpacks (heroku builder)
      - run: pack build ghcr.io/${GITHUB_REPOSITORY}/${IMG_NAME} --builder=heroku/buildpacks:20
      # Pushes your new image to Github's Container Registry
      - run: docker push ghcr.io/${GITHUB_REPOSITORY}/${IMG_NAME}
      # Copy the build assets from the container and cleanup
      - run: CONTAINER_ID=$(docker create ghcr.io/${GITHUB_REPOSITORY}/${IMG_NAME}:latest /bin/sh) &amp;amp;&amp;amp; docker cp ${CONTAINER_ID}:/workspace/public . &amp;amp;&amp;amp; docker rm ${CONTAINER_ID}
      - run: ls -alh public
      - uses: actions/upload-artifact@v2
        with:
          name: public
          path: public/

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It goes without saying that you need to add, commit and push this change afterwards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Deploy to AWS S3&lt;/li&gt;
&lt;li&gt;Deploy to Github Pages&lt;/li&gt;
&lt;li&gt;Deploy to Neocities&lt;/li&gt;
&lt;li&gt;Deploy to Netlify&lt;/li&gt;
&lt;li&gt;Deploy to SFTP&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Deploy to AWS S3
&lt;/h3&gt;

&lt;p&gt;Amazon's S3 is one of the oldest managed cloud service and it shows why. It's capable to act as a bin for all your files, yet also supports things like static hosting and rewrites.&lt;/p&gt;

&lt;h4&gt;
  
  
  Create a bucket
&lt;/h4&gt;

&lt;p&gt;Log on to AWS management console and find the S3 page. Create a bucket and note the ARN name.&lt;/p&gt;

&lt;h4&gt;
  
  
  IAM &amp;amp; access control
&lt;/h4&gt;

&lt;p&gt;One of the most painful AWS experiences, is how we deal with permissions. I've done this many times, but I have to look it up every time.&lt;/p&gt;

&lt;p&gt;Create a user with IAM and assign the following permissions with a new policy. Replace  with your ARN from earlier. Mine is &lt;em&gt;"arn:aws:s3:::jamstack-example.andri.dk/"&lt;/em&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
   &lt;strong&gt;jamstack-policy&lt;/strong&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:ListBucket",
                "s3:DeleteObject",
                "s3:PutObjectAcl"
            ],
            "Resource": [
                "&amp;lt;MYBUCKET&amp;gt;",
                "&amp;lt;MYBUCKET&amp;gt;/*"
            ]
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This policy allows your user to upload, download, list files, delete objects and upload with permissions (rclone uses this to apply your acl). The resources limit this permission to this single bucket (a good idea!).&lt;/p&gt;

&lt;h4&gt;
  
  
  TLS / SSL
&lt;/h4&gt;

&lt;p&gt;Some services do this automatically, but AWS requires you to do this manually. I didn't add TLS to my example site, but you can take a look at this &lt;a href="https://towardsdatascience.com/static-hosting-with-ssl-on-s3-a4b66fb7cd00?gi=2d84a6cc94b4"&gt;this blog post&lt;/a&gt; on how to add TLS to your S3 hosted site.&lt;/p&gt;

&lt;h4&gt;
  
  
  All done!
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1a1idcr5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/blog/2021/aws-deployment.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1a1idcr5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/blog/2021/aws-deployment.png" alt="Github Action results"&gt;&lt;/a&gt;The site is now deployed on AWS S3.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oiMrTAou--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/blog/2021/aws-deployed.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oiMrTAou--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/blog/2021/aws-deployed.png" alt="Screenshot of the deployed site"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Doing with any S3/SFTP any site isn't much different from the S3 example. In the appendix, I'll provide a few more examples.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  deploy-aws-s3:
    needs: remote-build
    runs-on: ubuntu-latest
    env:
      RCLONE_CONFIG_AWSS3_TYPE: s3
      RCLONE_CONFIG_AWSS3_ACCESS_KEY_ID: ${{ secrets.S3_KEY_ID }}
      RCLONE_CONFIG_AWSS3_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}
      RCLONE_CONFIG_AWSS3_REGION: eu-central-1 # yours may differ
      RCLONE_CONFIG_AWSS3_ACL: "public-read"
      BUCKETNAME: jamstack-example.andri.dk
    steps:
      - uses: andrioid/setup-rclone-action@main
      - uses: actions/download-artifact@v2
        with:
          name: public
          path: public
      - run: ls -alh
      - run: rclone ls awss3:${BUCKETNAME}
      - run: rclone -v copy public awss3:${BUCKETNAME}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is how my deploy-aws-s3 job looks in my workflow file.&lt;/p&gt;

&lt;p&gt;You can see the site in action &lt;a href="http://jamstack-example.andri.dk/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy to Netlify
&lt;/h3&gt;

&lt;p&gt;To be able to upload files to Netlify, you have to create a new site. To create a site without adding a Git repo, you can go to the "Sites" page and in the bottom there is a drag-drop area where you can drop your "public" directory to create a new site.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1AcM2b_I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/blog/2021/netlify-new-site.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1AcM2b_I--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/blog/2021/netlify-new-site.png" alt="creating a new site manually on Netlify"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When that is done, you can use the following job definition. Just grab the &lt;em&gt;Site API ID&lt;/em&gt; from the site-settings page.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  deploy-netlify:
    needs: remote-build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v2
        with:
          name: public
          path: public
      - uses: jsmrcaga/action-netlify-deploy@v1.1.0
        with:
          NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_TOKEN }}
          NETLIFY_DEPLOY_TO_PROD: true
          NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
          BUILD_DIRECTORY: public
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see the site in action &lt;a href="https://jamstack-example-andri.netlify.app"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy to Github Pages
&lt;/h3&gt;

&lt;p&gt;Github Pages uses Git, and if you have a project that you want to use for Github Pages you can just create an empty "gh-pages" branch and use it to update your site.&lt;/p&gt;

&lt;p&gt;For this blog-post, I created "andrioid/jamstack-example" and I'm using that for a demo deployment.&lt;/p&gt;

&lt;p&gt;To setup our pages branch, do the following. &lt;a href="https://stackoverflow.com/a/34100189/99209"&gt;Credit&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git checkout --orphan gh-pages
git rm -rf .
git commit --allow-empty -m "root commit"
git push -u origin gh-pages

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a branch named "gh-pages" without any commit history. Then deletes all of the tracked files. Then we add an empty commit and push it. Github will automatically set Pages up when the branch is named "gh-pages".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VZCiOERN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/blog/2021/ghpages.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VZCiOERN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/blog/2021/ghpages.png" alt="github pages setting"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The only remaining thing is to use our build-step to upload the site to this branch. Fortunately there is &lt;a href="https://github.com/JamesIves/github-pages-deploy-action"&gt;an action&lt;/a&gt; that does just that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  deploy-ghpages:
    needs: remote-build
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2 # needed to setup git
      - uses: actions/download-artifact@v2
        with:
          name: public
          path: public
      - uses: JamesIves/github-pages-deploy-action@4.1.1
        with:
          branch: gh-pages
          folder: public
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add this job to &lt;strong&gt;your deploy-anywhere.yaml&lt;/strong&gt; file.&lt;/p&gt;

&lt;p&gt;You can see the site in action &lt;a href="https://andrioid.github.io/jamstack-example"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy to Neocities
&lt;/h3&gt;

&lt;p&gt;Neocities offers free hosting and if you upgrade you can have TLS and a custom domain as well. They also support Webdav and that would allow us to upload with rclone (just like we did with S3), but I don't have a premium account.&lt;/p&gt;

&lt;p&gt;So, to get this running we grab the &lt;a href="https://github.com/marketplace/actions/deploy-to-neocities"&gt;Deploy to Neocities action&lt;/a&gt;. You can find your API key in the Neocities Site Settings. Add it to your repo secrets as "NEOCITIES_API_KEY".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  deploy-neocities:
    needs: remote-build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v2
        with:
          name: public
          path: public
      - name: Deploy to neocities
        uses: bcomnes/deploy-to-neocities@v1
        with:
          api_token: ${{ secrets.NEOCITIES_API_KEY }}
          cleanup: false
          dist_dir: public
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add this job to &lt;strong&gt;your deploy-anywhere.yaml&lt;/strong&gt; file.&lt;/p&gt;

&lt;p&gt;You can see the site in action &lt;a href="https://jamstack-example.neocities.org/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy to SFTP
&lt;/h3&gt;

&lt;p&gt;I setup a small webserver with automatic TLS on DigitalOcean to demonstrate SFTP. You can read about it &lt;a href="https://dev.to/andrioid/host-static-websites-on-a-vps-with-tls-m24"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You need the following secrets set up on your repo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SFTP_HOST&lt;/strong&gt; : host or ip of sftp server&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SFTP_USER&lt;/strong&gt; : webuser&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SFTP_PASS&lt;/strong&gt; : yourverysecretpassword&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Add this job to your &lt;strong&gt;deploy-anywhere.yaml&lt;/strong&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  deploy-via-sftp:
    needs: remote-build
    runs-on: ubuntu-latest
    env:
      RCLONE_CONFIG_VPS_TYPE: sftp
      RCLONE_CONFIG_VPS_HOST: ${{ secrets.SFTP_HOST }}
      RCLONE_CONFIG_VPS_USER: ${{ secrets.SFTP_USER }}
      RCLONE_CONFIG_VPS_PASS_PLAIN: ${{ secrets.SFTP_PASS }}
    steps:
      - uses: andrioid/setup-rclone-action@main
      - uses: actions/download-artifact@v2
        with:
          name: public
          path: public
      - run: echo "RCLONE_CONFIG_VPS_PASS=$(rclone obscure ${RCLONE_CONFIG_VPS_PASS_PLAIN})" &amp;gt;&amp;gt; $GITHUB_ENV
      - run: rclone -v copy public vps:.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see the site in action &lt;a href="https://hostingdemo.andri.dk"&gt;here&lt;/a&gt;. This link may stop working when I decide that I don't want to pay $5/mo for it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Hosting Providers?
&lt;/h3&gt;

&lt;p&gt;Please drop me a line on &lt;a href="https://twitter.com/andrioid"&gt;Twitter&lt;/a&gt; if you want to add a hosting provider to this post. The only requirement is that it's possible to create the project without Git integration.&lt;/p&gt;

&lt;h4&gt;
  
  
  Deploy to Vercel 🚫️
&lt;/h4&gt;

&lt;p&gt;Before Vercel was Zeit, their CLI &lt;code&gt;now&lt;/code&gt; was able to take any folder and make it a website. But, it doesn't look like Vercel allows any of that anymore. I also can't find a way to create a project without integrating with a Git hosting provider.&lt;/p&gt;

&lt;h4&gt;
  
  
  Deploy to DigitalOcean 🚫️
&lt;/h4&gt;

&lt;p&gt;DigitalOcean Spaces was actually my first attempt, but only after getting almost everything working, I realized that &lt;a href="https://www.digitalocean.com/community/questions/spaces-set-index-html-as-default-landing-page"&gt;they don't support indexes&lt;/a&gt;. So, I had everything up and running with their CDN, but I had to manually link to "index.html".&lt;/p&gt;

&lt;p&gt;As far as I know, there is no way of manually uploading files to their "Static Apps" offering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final words
&lt;/h2&gt;

&lt;p&gt;When I started writing this post, I was sure that most of the hosting providers &lt;em&gt;did&lt;/em&gt; in fact support open standards like SFTP, Webdav or even S3. But, that's not the case and that makes me sad.&lt;/p&gt;

&lt;p&gt;Why those providers have the need to dominate our workflows is beyond my understanding. After all, what we're uploading here isn't any different than classic websites. All we need is a web-server. Everything else is just gravy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Appendix
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How to copy files from a Docker image
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker create $image # returns container ID
docker cp $container_id:$source_path $destination_path
docker rm $container_id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>frontend</category>
      <category>devops</category>
      <category>linux</category>
    </item>
    <item>
      <title>Adding Progressive Web Apps (PWA) to the Linux Desktop</title>
      <dc:creator>Andri</dc:creator>
      <pubDate>Fri, 02 Oct 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/andrioid/adding-progressive-web-apps-pwa-to-the-linux-desktop-ik</link>
      <guid>https://dev.to/andrioid/adding-progressive-web-apps-pwa-to-the-linux-desktop-ik</guid>
      <description>&lt;p&gt;At work, we use Microsoft Teams, and we used to (and sort of still) use Slack. I recently switched from a Macbook Pro to a Linux laptop (again), and what annoys me greatly is that when I "share screen/application" in the Slack or Teams app it offers me only to share my entire monitor.&lt;/p&gt;

&lt;p&gt;Sure, it's annoying that I can't pick what window is shared; but what makes it unbearable for other people on my team is that my monitor is ultra-wide so everything I share becomes really tiny on their screen. Sorry.&lt;/p&gt;

&lt;h3&gt;
  
  
  Chrome can do it!
&lt;/h3&gt;

&lt;p&gt;By accident, I stumbled upon that Slack running in Chrome on Linux can share windows just fine. The native Slack app does not. So I went on an adventure to figure out how to add web-apps to the Linux desktop and it's surprisingly hard to find (hence this post).&lt;/p&gt;

&lt;p&gt;Chrome used to have a link in "More Tools -&amp;gt; Add to Desktop", but no more; and that had me confused. It's now called "More Tools -&amp;gt; Create Shortcut".&lt;/p&gt;

&lt;p&gt;&lt;a href="///static/121d9a42bed8ee3fd72f0bfa24790545/321ea/create-shortcut-chrome.png"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CiqppYaZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/static/121d9a42bed8ee3fd72f0bfa24790545/321ea/create-shortcut-chrome.png" alt="Create Shortcut in Chrome" title="Create Shortcut in Chrome"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just navigate to the site you want to open as an application, click "Create Shortcut" from the menu, check "Open as Window" and you're good.&lt;/p&gt;

&lt;p&gt;On Linux, this will create a &lt;code&gt;.desktop&lt;/code&gt; file in &lt;code&gt;~/.local/applications&lt;/code&gt; so that your desktop environment will pick it up. This guide should also work fine on Windows and Mac.&lt;/p&gt;

&lt;h3&gt;
  
  
  Plus, it's better!
&lt;/h3&gt;

&lt;p&gt;Today, I'm running Outlook, Slack and Teams as progressive-web-applications and the performance is much better, screen/window sharing works and it uses less CPU than the Electron apps.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>pwa</category>
    </item>
    <item>
      <title>Kubernetes: Set Image Action</title>
      <dc:creator>Andri</dc:creator>
      <pubDate>Thu, 27 Aug 2020 11:32:13 +0000</pubDate>
      <link>https://dev.to/andrioid/kubernetes-set-image-action-2c2k</link>
      <guid>https://dev.to/andrioid/kubernetes-set-image-action-2c2k</guid>
      <description>&lt;h3&gt;
  
  
  My Workflow
&lt;/h3&gt;

&lt;p&gt;I have a workflow for work (private repo) that required setting images on Kubernetes clusters and the workflow file was getting harder and harder to read.&lt;/p&gt;

&lt;p&gt;So I decided to create &lt;a href="https://github.com/marketplace/actions/kubernetes-set-image"&gt;Kubernetes: Set Image&lt;/a&gt; that generates &lt;code&gt;kubectl&lt;/code&gt; commands to help with changing images on deployments, and wait until the changes take effect.&lt;/p&gt;

&lt;p&gt;I'll be using this for automatic environments, manual dispatch for more stable environments.&lt;/p&gt;

&lt;h4&gt;
  
  
  Features
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Supports multiple deployments&lt;/li&gt;
&lt;li&gt;Supports multiple containers per deployment&lt;/li&gt;
&lt;li&gt;Groups deployment commands, so if you have two changes to one deployment, it will only run one kubectl line.&lt;/li&gt;
&lt;li&gt;Wait until the cluster reports that the changes have been applied.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Submission Category:
&lt;/h3&gt;

&lt;p&gt;DIY Deployments&lt;/p&gt;

&lt;h3&gt;
  
  
  Yaml File or Link to Code
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://github.com/andrioid/gh-action-k8s-set-image"&gt;README&lt;/a&gt; has examples of how to use this action.&lt;/p&gt;

&lt;p&gt;Example workflow&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;workflow.yml&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Testing&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Test our action&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;andrioid/gh-action-k8s-set-image&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;default"&lt;/span&gt;
          &lt;span class="na"&gt;images&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;deployment.example.backend=gcr.io/example/backend:latest&lt;/span&gt;
            &lt;span class="s"&gt;deployment.example.frontend=gcr.io/example/frontend:latest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl --namespace default set image deployment/example backend=gcr.io/example/backend:latest frontend=gcr.io/example/frontend:latest
kubectl --namespace default rollout status deployment/example
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Additional Resources / Info
&lt;/h3&gt;

&lt;p&gt;Special thanks to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.typescriptlang.org/"&gt;TypeScript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/vercel/ncc"&gt;NCC (from Vercel)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kulshekhar/ts-jest"&gt;ts-jest&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Venue POS (my employer)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>actionshackathon</category>
      <category>kubernetes</category>
      <category>devops</category>
    </item>
    <item>
      <title>Meetings from Home: Tools</title>
      <dc:creator>Andri</dc:creator>
      <pubDate>Mon, 16 Mar 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/andrioid/meetings-from-home-tools-59f6</link>
      <guid>https://dev.to/andrioid/meetings-from-home-tools-59f6</guid>
      <description>&lt;p&gt;Are you working from home? Here's a list of some great tools that you can use for free.&lt;/p&gt;

&lt;p&gt;If you have anything to add to the list, please give me a shout at &lt;a href="https://twitter.com/andrioid"&gt;@andrioid&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Video Conferencing and chat
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://jitsi.org/jitsi-meet/"&gt;Jitsi Meet&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Not quite as polished as some of the commercial options, but quite capable. Works best with Chrome, but seemed to work fine in Firefox too.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No account required: ✅&lt;/li&gt;
&lt;li&gt;No organization required: ✅&lt;/li&gt;
&lt;li&gt;Open Source: ✅&lt;/li&gt;
&lt;li&gt;Self Hostable: ✅&lt;/li&gt;
&lt;li&gt;Screen Sharing: ✅&lt;/li&gt;
&lt;li&gt;Chat: ✅&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://team.video"&gt;Team.video&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Does exactly what the names says. No account required, very simple to use.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No account required: ✅&lt;/li&gt;
&lt;li&gt;No organization required: ✅&lt;/li&gt;
&lt;li&gt;Open Source: 🚫&lt;/li&gt;
&lt;li&gt;Self Hostable: 🚫&lt;/li&gt;
&lt;li&gt;Screen Sharing: ✅&lt;/li&gt;
&lt;li&gt;Chat: ✅&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://whereby.com"&gt;Whereby&lt;/a&gt; (formerly Appear.in)
&lt;/h3&gt;

&lt;p&gt;Used to be my favourite video-conferencing tool.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No account required: 🚫&lt;/li&gt;
&lt;li&gt;No organization required: ✅&lt;/li&gt;
&lt;li&gt;Open Source: 🚫&lt;/li&gt;
&lt;li&gt;Self Hostable: 🚫&lt;/li&gt;
&lt;li&gt;Screen Sharing: ✅&lt;/li&gt;
&lt;li&gt;Chat: ✅&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://slack.com"&gt;Slack&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Needs no introduction. A chat app that also has voice and video features.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No account required: 🚫&lt;/li&gt;
&lt;li&gt;No organization required: 🚫&lt;/li&gt;
&lt;li&gt;Open Source: 🚫&lt;/li&gt;
&lt;li&gt;Self Hostable: 🚫&lt;/li&gt;
&lt;li&gt;Screen Sharing: ✅&lt;/li&gt;
&lt;li&gt;Chat: ✅&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://teams.microsoft.com/start"&gt;Microsoft Teams&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Microsoft's answer to Slack. Needs your organization to have a Microsoft account and the Teams feature activated.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No account required: 🚫&lt;/li&gt;
&lt;li&gt;No organization required: 🚫&lt;/li&gt;
&lt;li&gt;Open Source: 🚫&lt;/li&gt;
&lt;li&gt;Self Hostable: 🚫&lt;/li&gt;
&lt;li&gt;Screen Sharing: ✅&lt;/li&gt;
&lt;li&gt;Chat: ✅&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Whiteboarding
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://excalidraw.com"&gt;Excalidraw&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;An excellent tool that makes it quite easy to draw diagrams or simple drawings.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Diagrams: ✅&lt;/li&gt;
&lt;li&gt;Live collaboration: ✅&lt;/li&gt;
&lt;li&gt;Free form: 🚫&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>workingfromhome</category>
      <category>tools</category>
      <category>career</category>
      <category>productivity</category>
    </item>
    <item>
      <title>2019 in Tech</title>
      <dc:creator>Andri</dc:creator>
      <pubDate>Sun, 29 Dec 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/andrioid/2019-in-tech-448a</link>
      <guid>https://dev.to/andrioid/2019-in-tech-448a</guid>
      <description>&lt;p&gt;Every year, or that was my plan back in 2013; I do a review of what technology I've been using during the year and reflect on my experiences.&lt;/p&gt;

&lt;p&gt;This year has been fun in many ways. I have been creating apps, websites, microservices and APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  React
&lt;/h2&gt;

&lt;p&gt;When I started playing with React 4 years ago, Angular was insanely popular in Denmark and everyone was looking at me funny. There was no standard tool for anything and every week there were major version changes in the stack.&lt;/p&gt;

&lt;p&gt;I heard this somewhere:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;People use Angular because of Google. People use React even if Facebook made it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Today, I look back on React with fondness. I started making web-sites back when we had &lt;code&gt;.shtml&lt;/code&gt; files and &lt;code&gt;.php3&lt;/code&gt;. But then something changed. We as an industry started using the browser for more than rendering markup and then we had a few broken years.&lt;/p&gt;

&lt;p&gt;The core problem, as I see it; is that we no longer have enough information to fully generate web-pages on the server anymore. The users expect a certain amount of dynamic functionality and while it isn't hard to add small snippets, this quickly becomes unmaintainable.&lt;/p&gt;

&lt;p&gt;Some people realized this and tried to combine one templating language for both server and client. But as soon as the initial data started to change, there were issues.&lt;/p&gt;

&lt;p&gt;React changed all of this. We now have one language to describe UI as a function of props and state. I can't imagine doing UI any other way. It might be Svelte or Vue in the future; but components are here to stay.&lt;/p&gt;

&lt;h2&gt;
  
  
  Go, the programming language
&lt;/h2&gt;

&lt;p&gt;I'm sure some people consider me an early adopter, but I had my eye on Go for a very long time before I started using it professionally 3 years ago.&lt;/p&gt;

&lt;p&gt;It's funny how I can be excited for a language that has very few features, roughly 6 years after I first started playing around with it.&lt;/p&gt;

&lt;p&gt;It feels like being on the same team. We all use the same formatting, the standard library usually has the functionality we need and there is less friction when working with others. This contributes to what I consider to be a very productive language.&lt;/p&gt;

&lt;p&gt;In 2019 I have created GraphQL API's, microservices and small tools and Go is by far my favourite backend language.&lt;/p&gt;

&lt;h2&gt;
  
  
  TypeScript
&lt;/h2&gt;

&lt;p&gt;In May, I joined a new company and they were doing all of their new code in TypeScript. The project I joined has &lt;code&gt;alwaysStrict=true&lt;/code&gt; and I hated it very much. Everything I was used to do in JavaScript was now something that the compiler refused to make work. I spent 2/3 of the time defining types for something I thought was obvious.&lt;/p&gt;

&lt;p&gt;Later, I moved to another project where we also did TypeScript with React, but without strict-mode this time. This time, it clicked for me. It helped that I had 6 months experience with TypeScript at the time, but being free to &lt;em&gt;use TypeScript&lt;/em&gt; without the feeling that I was being used by it.&lt;/p&gt;

&lt;p&gt;Bottom line. Would I use TypeScript again? Yes. Being able to define types for props and state really speeds up development. Strict-mode? It's too verbose and results in some very ugly code without any good reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  Immer
&lt;/h2&gt;

&lt;p&gt;I first heard about &lt;a href="https://github.com/immerjs/immer"&gt;Immer&lt;/a&gt; over at React Europe 2018, in the context of being "better than Immutable.js", but I had absolutely no intention of doing anything, that changed all of my state data to some weird types again. So I ignored it.&lt;/p&gt;

&lt;p&gt;Then, I was dealing with some internal state in React that included messing with items of an array. Arrays in React state is probably the most annoying part of React. It feels verbose and hard to read at the same time. This immutable dance with arrays is painful; even in modern JS or TS.&lt;/p&gt;

&lt;p&gt;Immer is lovely. It's a function that takes the current state, gives you a "draft-state" that you can mutate. Then it produces a new output and returns it. The best part&lt;/p&gt;

&lt;p&gt;So you can simply do&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import produce from 'immer'
const [animals, setAnimals] = useState(['Cock', 'Raven', 'Dog', 'Pig']);
setAnimals(produce(draftAnimals =&amp;gt; {
    draftItems[0] = 'Rooster'
}))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will produce a new array with the changes applied and React will render the changes as expected.&lt;/p&gt;

&lt;p&gt;Immer is a must-have tool in your React toolbox.&lt;/p&gt;

&lt;h2&gt;
  
  
  React Native
&lt;/h2&gt;

&lt;p&gt;I used to work at a company that does mobile-payments for parking. They had an app that was once a nice Xamarin application that worked on iOS and Android. But after 2 years of out-sourcing the app was unstable and unmaintainable (we spent 4 months on just making it stop crashing, without luck). I was responsible for the project and I had a feeling in my stomach that this was probably never going to be ok unless we'd invest an entire team into this.&lt;/p&gt;

&lt;p&gt;I had tried playing with React Native when 2 years earlier, when they released initial support for Android, but it didn't feel mature at all. So, I decided to give it another shot and spent a few evenings and a weekend making a simple proof-of-concept in React Native. Long story short: I pitched it to my boss and we created a new app in React Native and Expo.&lt;/p&gt;

&lt;p&gt;Expo was a really positive experience for me. I didn't have a big team, so being able to skip the native part of React Native was a clear win for me. All of the libraries we needed were already in Expo, so it was a no-brainer.&lt;/p&gt;

&lt;p&gt;So when I started on a new React Native project this year, where we had to do all of the native parts ourselves, I was a bit scared. To my surprise, it wasn't as hard as I thought. I managed to do some minor changes to a Swift library without knowing anything about Swift or Xcode with help from my co-workers. And making the native bits talk to the JS bits was quite simple too.&lt;/p&gt;

&lt;p&gt;All in all, a positive experience. Would do again.&lt;/p&gt;

&lt;h2&gt;
  
  
  For 2020?
&lt;/h2&gt;

&lt;p&gt;I'm starting the year with by joining an existing React Native team and I'm looking forward to help bring the project forward.&lt;/p&gt;

&lt;p&gt;So here's hoping for another year filled with good technology. I wish you a happy new year!&lt;/p&gt;

</description>
      <category>react</category>
      <category>go</category>
      <category>programming</category>
      <category>tech</category>
    </item>
    <item>
      <title>Announcing gatsby-plugin-social-cards</title>
      <dc:creator>Andri</dc:creator>
      <pubDate>Sun, 15 Sep 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/andrioid/announcing-gatsby-plugin-social-cards-3p85</link>
      <guid>https://dev.to/andrioid/announcing-gatsby-plugin-social-cards-3p85</guid>
      <description>&lt;p&gt;Ever seen those images shown next to posts on Twitter, Slack or Facebook? Ever wonder where they come from? Me too!&lt;/p&gt;

&lt;p&gt;This plugin will create automatic cards for your (ReMark) at build-time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Announcing
&lt;/h2&gt;

&lt;p&gt;I am ready to announce my new Gatsby plugin, gatsby-plugin-social-cards.&lt;/p&gt;

&lt;p&gt;There are a few alternatives for Gatsby social-cards, but I wanted to try making these cards with SVG, React and the Sharp library that most Gatsby sites already depend on.&lt;/p&gt;

&lt;p&gt;Currently this plugin resides within my site, but if there is interest in it, I'll move it into its own repo. Contact me on Twitter if you have any issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Backgrounds
&lt;/h3&gt;

&lt;p&gt;You can put a cover frontmatter on your post, and we'll use that. Otherwise, we'll use a default-background that you can specify or if that fails, we'll use a fallback one.&lt;/p&gt;

&lt;p&gt;&lt;a href="///static/34221344967eb655433086323a4b2d01/c35de/default-design.jpg"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2DSz0JjV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/static/34221344967eb655433086323a4b2d01/b1910/default-design.jpg" alt="default card design" title="default card design"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Designs
&lt;/h3&gt;

&lt;p&gt;There are two design available now, "card" and "default". But we can expand that later.&lt;/p&gt;

&lt;p&gt;&lt;a href="///static/7987758a52d337e319646b91587e15c4/c35de/card-design.jpg"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_4K7AG3t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/static/7987758a52d337e319646b91587e15c4/b1910/card-design.jpg" alt="default card design" title="default card design"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Author image
&lt;/h3&gt;

&lt;p&gt;If specified, an author image is shown on the image. That is also configurable.&lt;/p&gt;

&lt;p&gt;&lt;a href="///static/ac9793437536b1aee7d53ef2f3a1801b/c35de/cover-custom-author.jpg"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--shMOM33G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/static/ac9793437536b1aee7d53ef2f3a1801b/b1910/cover-custom-author.jpg" alt="default card design" title="default card design"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom author-image
&lt;/h3&gt;

&lt;p&gt;The author image can be ommitted if it's not wanted, or customized in the configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add gatsby-plugin-social-cards
# or npm install --save gatsby-plugin-social-cards
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to use
&lt;/h2&gt;

&lt;p&gt;Please see the &lt;a href="https://www.npmjs.com/package/gatsby-plugin-social-cards"&gt;README&lt;/a&gt; for how to use.&lt;/p&gt;

</description>
      <category>gatsby</category>
      <category>react</category>
    </item>
    <item>
      <title>Slim Docker images for your Go application</title>
      <dc:creator>Andri</dc:creator>
      <pubDate>Fri, 06 Sep 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/andrioid/slim-docker-images-for-your-go-application-11oo</link>
      <guid>https://dev.to/andrioid/slim-docker-images-for-your-go-application-11oo</guid>
      <description>&lt;p&gt;How to build a slim Docker container for your Go application using multi-stage images.&lt;/p&gt;

&lt;p&gt;I'm using Go 1.13 and the community module proxy to build the binary and Alpine as a base image. It adds a user and group instead of running as root.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dockerfile
&lt;/h3&gt;

&lt;p&gt;Be sure to replace "cmd/server/server.go" with your main file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM golang:1.13 as builder

WORKDIR /app
COPY . /app
RUN CGO_ENABLED=0 GOOS=linux GOPROXY=https://proxy.golang.org go build -o app cmd/server/server.go

FROM alpine:latest
# mailcap adds mime detection and ca-certificates help with TLS (basic stuff)
RUN apk --no-cache add ca-certificates mailcap &amp;amp;&amp;amp; addgroup -S app &amp;amp;&amp;amp; adduser -S app -G app
USER app
WORKDIR /app
COPY --from=builder /app/app .
ENTRYPOINT ["./app"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I hope this is helpful. I mostly blogged this to document it for myself.&lt;/p&gt;

</description>
      <category>go</category>
      <category>docker</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Making a Gatsby site from Meetup event data</title>
      <dc:creator>Andri</dc:creator>
      <pubDate>Sun, 25 Aug 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/andrioid/making-a-gatsby-site-from-meetup-event-data-208i</link>
      <guid>https://dev.to/andrioid/making-a-gatsby-site-from-meetup-event-data-208i</guid>
      <description>&lt;p&gt;As part of my talk preperation for Aalborg Frontend Meetup, I was going to do a walkthrough of a new Gatsby site and use &lt;a href="https://www.gatsbyjs.org/packages/gatsby-source-meetup/?=meetup"&gt;gatsby-source-meetup&lt;/a&gt; to import the upcoming events.&lt;/p&gt;

&lt;p&gt;Imagine my surprise, when I discovered that Meetup had stopped issuing API keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  gatsby-source-ical
&lt;/h2&gt;

&lt;p&gt;Gatsby has a wonderful collection of source plugins, and one of them happens to be &lt;a href="https://www.gatsbyjs.org/packages/gatsby-source-ical/?=ical"&gt;gatsby-source-ical&lt;/a&gt;. I'll hand it to Meetup support; they did send me a useful link that indicated they I may not need an API, since they have a calendar feed available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding the calendar export
&lt;/h2&gt;

&lt;p&gt;First, find your group on &lt;a href="https://www.meetup.com"&gt;Meetup&lt;/a&gt;, click "Events", "Calendar" and at the bottom there are feed links (if you're logged in).&lt;/p&gt;

&lt;p&gt;For our group, the URL is: "&lt;a href="https://www.meetup.com/Aalborg-Frontend/events/ical/"&gt;https://www.meetup.com/Aalborg-Frontend/events/ical/&lt;/a&gt;"&lt;/p&gt;

&lt;h2&gt;
  
  
  Add the plugin
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add gatsby-source-ical
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  gatsby-config.js
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// In your gatsby-config.js
module.exports = {
    plugins: [
        // You can have multiple instances of this plugin
        // to read source nodes from different remote files
        {
            resolve: `gatsby-source-ical`,
            options: {
                name: `events`,
                url: `https://web-standards.ru/calendar.ics`
            }
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Query the data
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    allIcal {
        edges {
            node {
                start
                end
                summary
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why?
&lt;/h2&gt;

&lt;p&gt;I put this together in case someone else is creating a meetup website with Gatsby and got frustrated with the API change.&lt;/p&gt;

</description>
      <category>gatsby</category>
      <category>react</category>
      <category>meetup</category>
    </item>
    <item>
      <title>Choosing a programming language</title>
      <dc:creator>Andri</dc:creator>
      <pubDate>Sun, 04 Aug 2019 00:00:00 +0000</pubDate>
      <link>https://dev.to/andrioid/choosing-a-programming-language-3nme</link>
      <guid>https://dev.to/andrioid/choosing-a-programming-language-3nme</guid>
      <description>&lt;p&gt;Imagine that you're a CTO, a Tech Lead or someone that has to make an early technical decision that will effect the company for years to come. What programming language do you choose?&lt;/p&gt;

&lt;h1&gt;
  
  
  The cost of abstraction
&lt;/h1&gt;

&lt;p&gt;Systems tend to have two types of complexity. Complexity from the business-domain, and complexity from the technical-domain. In my experience, the technical-complexity takes up more room than it should.&lt;/p&gt;

&lt;p&gt;There are, of course brilliant programmers out there, who manage complexity really well, regardless of what language they code in. But people have different skill-sets, and a great team has many different types of members.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--B_zQOMOZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/features2-c9de6951f9efbc94e39a14a5adac156b.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--B_zQOMOZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/features2-c9de6951f9efbc94e39a14a5adac156b.svg" alt="features"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Readability
&lt;/h1&gt;

&lt;p&gt;When I took my first programming job, I was looking forward to "writing some awesome code". But, writing code wasn't my primary job. Reading it was.&lt;/p&gt;

&lt;p&gt;There are some tools that help manage this. Coding-standards, linters, code-formatters all try to make reading each other's code easier.&lt;/p&gt;

&lt;p&gt;But what if every feature of our programming language, comes with a cost? If you give 10 programmers the same task, how similar would the implementations be? I argue that if you get many different types of implementations, then your programming language is too complex.&lt;/p&gt;

&lt;p&gt;To be productive at writing code, we also need to be productive at reading it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3efy0QBH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/code-review-b08868966c94c90df586a26a8fded690.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3efy0QBH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/code-review-b08868966c94c90df586a26a8fded690.svg" alt="features"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Dependencies
&lt;/h1&gt;

&lt;p&gt;At a certain point we need something from our programming language that isn't a language-feature, but a library feature. We need to open a file, parse HTTP headers, decode JSON, or talk to databases.&lt;/p&gt;

&lt;p&gt;Every dependency we add to our programs comes with a price, even if the code is free. What if the maintainer get's hit by a bus? What if the maintainer becomes malicious? What if there's a critical security bug?&lt;/p&gt;

&lt;p&gt;Why did PHP win over Perl for web-development in the late 90's? I'm willing to bet it was because PHP's standard-library was so good, and well documented.&lt;/p&gt;

&lt;p&gt;Another approach is Javascript's NPM where there is no standard library. Every little thing has it's own package. The advantage has been that the community of JS really stepped up and you can find a package for any thing you can imagine. The disadvantage is that with nested dependencies, we have no idea on what code we're actually depending on. There hasn't been a NPM apocalypse yet, just the story of left-pad and an occasional bitcoin miner.&lt;/p&gt;

&lt;p&gt;I think this a no-brainer. If we're building something that needs to be reliable for years to come, then we need a standard library that has security updates and advisories when shit happens. And remember, that we don't include dependencies, we adopt them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SeWsdV9x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/dependencies-7a14759d6a794492689c15fae7746e10.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SeWsdV9x--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/dependencies-7a14759d6a794492689c15fae7746e10.svg" alt="dependencies"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Hiring people
&lt;/h1&gt;

&lt;p&gt;At some point, we all need help. And that help usually comes to us by means of hiring people. If it wasn't for this point, I might be programming in Erlang, Elixir and Elm right now. I think these languages are extremely interesting, and not just because they all start with the letter E.&lt;/p&gt;

&lt;p&gt;But reality sets in, and we need a pool of people to hire from, that either know your tech-stack or will have an easy time learning it.&lt;/p&gt;

&lt;p&gt;The exception is when an organization has decided to invest in something, has a resources to teach newcomers and is willing to pay premium for talent.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3uX_c4ib--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/good-team-a40324920e0413294c1165ed202d5360.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3uX_c4ib--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://andri.dk/good-team-a40324920e0413294c1165ed202d5360.svg" alt="good-team by undraw"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Performance
&lt;/h1&gt;

&lt;p&gt;Performance is important. But, aim for "good enough" instead of "perfect". "Perfect" will almost certainily have a downside in other parts of this post.&lt;/p&gt;

&lt;p&gt;Does your project require some hard-core performance? Systems that have a "hard" real-time requirement might limit your choices. "Soft" real-time is still doable in almost any language.&lt;/p&gt;

&lt;p&gt;We once programmed in a world of a single CPU. That world is no more. Please pick a langauge that handles multiple executions well.&lt;/p&gt;

&lt;p&gt;A practical example would be a checkout-API call on a webshop. The customer has submitted a payment token, an order, and an email we need to verify. None of these things depend on each other, but all three things need to complete to succeed.&lt;/p&gt;

&lt;h1&gt;
  
  
  Developer Experience
&lt;/h1&gt;

&lt;p&gt;To me, a good developer experience consists of the following things.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The code is easy to read and understand.&lt;/li&gt;
&lt;li&gt;The language is supported in my editor (or IDE) of choice.&lt;/li&gt;
&lt;li&gt;There is a good, &lt;em&gt;reliable&lt;/em&gt; debugger available.&lt;/li&gt;
&lt;li&gt;Easy to write and run tests, both locally and in build-systems.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Final words
&lt;/h1&gt;

&lt;p&gt;We try to plan for the future, but nobody really knows it will bring. So, aim for the next 5 years and then time will tell.&lt;/p&gt;

&lt;p&gt;What language would you pick, and why?&lt;/p&gt;

&lt;h2&gt;
  
  
  Credits
&lt;/h2&gt;

&lt;p&gt;Big thanks to &lt;a href="https://twitter.com/ninalimpi"&gt;@NinaLimpi&lt;/a&gt; at &lt;a href="https://undraw.co"&gt;Undraw&lt;/a&gt; for the wonderful illustrations.&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
