<?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: Joseph Thomas</title>
    <description>The latest articles on DEV Community by Joseph Thomas (@goodidea).</description>
    <link>https://dev.to/goodidea</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%2F12646%2F11514928.png</url>
      <title>DEV Community: Joseph Thomas</title>
      <link>https://dev.to/goodidea</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/goodidea"/>
    <language>en</language>
    <item>
      <title>Setting up PM2 CI deployments with Github Actions</title>
      <dc:creator>Joseph Thomas</dc:creator>
      <pubDate>Wed, 26 Aug 2020 04:59:11 +0000</pubDate>
      <link>https://dev.to/goodidea/setting-up-pm2-ci-deployments-with-github-actions-1494</link>
      <guid>https://dev.to/goodidea/setting-up-pm2-ci-deployments-with-github-actions-1494</guid>
      <description>&lt;p&gt;I spent the afternoon wrestling with getting some Github actions set up for CI using &lt;a href="https://pm2.keymetrics.io/"&gt;pm2&lt;/a&gt;. I had some issues &amp;amp; confusion around SSH issues, and couldn't find anything online, so I wanted to post this here --- even if it's just for me to refer to later. In particular, I was dealing with &lt;code&gt;Host key verification failed.&lt;/code&gt; when attempting to deploy from the Github action.&lt;/p&gt;

&lt;p&gt;If you're not familiar with PM2, it's a process manager that will run your node.js apps, allowing you to stop, restart, and view logs.&lt;/p&gt;

&lt;p&gt;I'm deploying to a DigitalOcean droplet, but the following should be the same for any other VPS. Here are a few tutorials you can follow to recreate the same setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.digitalocean.com/community/tutorials/initial-server-setup-with-ubuntu-18-04"&gt;Set up a new Ubuntu droplet &amp;amp; sudo user&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-a-node-js-application-for-production-on-ubuntu-18-04"&gt;Set up Node.js &amp;amp; serve with Nginx&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.digitalocean.com/community/tutorials/how-to-use-pm2-to-setup-a-node-js-production-environment-on-an-ubuntu-vps"&gt;Set up PM2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you have your server set up and your repository hosted on Github, follow these steps:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Set up PM2 configuration
&lt;/h4&gt;

&lt;p&gt;Create or update &lt;code&gt;ecosystem.config.js&lt;/code&gt;. Mine looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;apps&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="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;myapp-api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;yarn start:api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;time&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="na"&gt;instances&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;autorestart&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="na"&gt;max_restarts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;max_memory_restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1G&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;DATABASE_ADDRESS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DATABASE_ADDRESS&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="na"&gt;deploy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;production&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;username&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;165.232.50.103&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;deploy.key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;origin/main&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://github.com/username/myapp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/home/username/myapp&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;post-deploy&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;yarn install &amp;amp;&amp;amp; yarn build &amp;amp;&amp;amp; pm2 reload ecosystem.config.js --env production &amp;amp;&amp;amp; pm2 save &amp;amp;&amp;amp; git checkout yarn.lock&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;NODE_ENV&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;DATABASE_ADDRESS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DATABASE_ADDRESS&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Make sure you aren't missing the &lt;code&gt;deploy.production.key&lt;/code&gt; value, and that your &lt;code&gt;ref&lt;/code&gt; matches the branch you want to deploy from on github. (I've renamed my &lt;code&gt;master&lt;/code&gt; branch to &lt;code&gt;main&lt;/code&gt;, see &lt;a href="https://github.com/good-idea/no-masters"&gt;npx no-masters&lt;/a&gt;)&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Create a SSH key pair
&lt;/h4&gt;

&lt;p&gt;To get things to work, we need to be able to ssh into our remote server from the machine running the github action. Our next step is to create some new SSH keys. (You could use ones you already have, but it's safer to create new ones just for this project).&lt;/p&gt;

&lt;p&gt;On your local machine, run:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ssh-keygen -t rsa -b 4096 -C "username@SERVER_IP" -q -N ""&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;When prompted about the file location, enter &lt;code&gt;gh_rsa&lt;/code&gt;, or anything else you'd like - we will delete these files after getting the information we need from them.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Remote server setup
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Copy the contents of the public file: &lt;code&gt;cat gh_rsa.pub | pbcopy&lt;/code&gt; (or if you don't have &lt;code&gt;pbcopy&lt;/code&gt;, just &lt;code&gt;cat&lt;/code&gt; and then copy from the terminal output).&lt;/li&gt;
&lt;li&gt;SSH into your remote server: &lt;code&gt;ssh username@SERVER_IP&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Add the public key to your user's &lt;code&gt;authorized_keys&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;echo "ssh-rsa AAAA....YOUR_PUBLIC_KEY..." &amp;gt;&amp;gt; ~/.ssh/authorized_keys&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Github Secrets setup
&lt;/h4&gt;

&lt;p&gt;Next, we'll add some secrets to the Github repo for use in the action. Go to your repo's Settings &amp;gt; Secrets pane, then add two new keys:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Secret key:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cat gh_rsa | pbcopy&lt;/code&gt; to copy the private key to your clipboard&lt;/li&gt;
&lt;li&gt;In Github, create a new secret named &lt;code&gt;SSH_PRIVATE_KEY&lt;/code&gt; and paste in the contents.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Known hosts:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ssh-keyscan SERVER_IP &amp;gt; pbcopy&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;In Github, create a new secret named &lt;code&gt;SSH_KNOWN_HOSTS&lt;/code&gt; and paste in the contents.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  5. Github Action configuration
&lt;/h4&gt;

&lt;p&gt;Lastly, on your local machine, create or update the file &lt;code&gt;.github/workflows/main.yml&lt;/code&gt; (or whatever file you are using for your action):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: CI - Master
  on:
    push:
      branches: [ main ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      #
      # ... your other steps, such as running tests, etc...
      #
      - name: Set up SSH
        run: |
          mkdir -p ~/.ssh/
          echo "$SSH_PRIVATE_KEY" &amp;gt; ./deploy.key
          sudo chmod 600 ./deploy.key
          echo "$SSH_KNOWN_HOSTS" &amp;gt; ~/.ssh/known_hosts
        shell: bash
        env:
          SSH_PRIVATE_KEY: ${{secrets.SSH_PRIVATE_KEY}}
          SSH_KNOWN_HOSTS: ${{secrets.SSH_KNOWN_HOSTS}}

      # (optional - only needed if your config uses environment variables)
      - name: Create env file
        run: |
          touch .env
          echo DATABASE_ADDRESS=${{ secrets.DATABASE_ADDRESS }} &amp;gt;&amp;gt; .env

      - name: Install PM2
        run: npm i pm2

      - name: Deploy
        run: env $(cat .env | grep -v \"#\" | xargs) pm2 deploy ecosystem.config.js staging
        # Or alternately, put this deploy script in your package.json's scripts and run it using yarn/npm:
        # run: yarn deploy
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The key steps here are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Set up SSH&lt;/strong&gt;: this creates the &lt;code&gt;deploy.key&lt;/code&gt; file, so when pm2 deploys, it matches the public key that we added to the server's &lt;code&gt;authorized_keys&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create Env File&lt;/strong&gt;: If your PM2 configuration uses environment variables, we need to use our Github secrets to populate &lt;code&gt;process.env&lt;/code&gt; with these variables. In our "Deploy" step, we use the &lt;code&gt;env&lt;/code&gt; command to load the file we created.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  6. Cleanup &amp;amp; security
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Add &lt;code&gt;deploy.env&lt;/code&gt; to your &lt;code&gt;.gitignore&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;Delete the &lt;code&gt;gh_rsa&lt;/code&gt; and &lt;code&gt;gh_rsa.pub&lt;/code&gt; files that we created. Most importantly: if you created your SSH keys within your project's directory, &lt;em&gt;be sure to not commit them&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;That should do it! If you have any issues, please leave a comment and I'll get back as soon as I can. :)&lt;/p&gt;

</description>
      <category>pm2</category>
      <category>github</category>
      <category>ci</category>
    </item>
    <item>
      <title>Show dev: `npx no-masters`</title>
      <dc:creator>Joseph Thomas</dc:creator>
      <pubDate>Sun, 05 Jul 2020 00:57:45 +0000</pubDate>
      <link>https://dev.to/goodidea/show-dev-npx-no-masters-4897</link>
      <guid>https://dev.to/goodidea/show-dev-npx-no-masters-4897</guid>
      <description>&lt;p&gt;There has been a lot of talk recently on the topic of renaming the default git repository branch to something other than &lt;code&gt;master&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I'm for it - and I made tool to make it easy:&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;code&gt;npx no-masters&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a copy of your &lt;code&gt;master&lt;/code&gt; branch and rename it to &lt;code&gt;main&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;(optionally) push this &lt;code&gt;main&lt;/code&gt; branch to github and update the default branch&lt;/li&gt;
&lt;li&gt;(optionally) delete the local and remote &lt;code&gt;master&lt;/code&gt; branches&lt;/li&gt;
&lt;li&gt;(optionally) set up your local git config to use &lt;code&gt;main&lt;/code&gt; as the default branch when creating new repositories with &lt;code&gt;git init&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Enjoy, and please share!&lt;/p&gt;

&lt;p&gt;If you have (constructive) feedback or come across any issues, DM me on twitter &lt;a href="https://twitter.com/typeof_goodidea"&gt;@typeof_goodidea&lt;/a&gt; or start an issue in the github repo: &lt;a href="https://github.com/good-idea/no-masters"&gt;https://github.com/good-idea/no-masters&lt;/a&gt;&lt;/p&gt;

</description>
      <category>blm</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Building Co-Touch: Goals &amp; Introduction</title>
      <dc:creator>Joseph Thomas</dc:creator>
      <pubDate>Tue, 07 Jan 2020 19:52:58 +0000</pubDate>
      <link>https://dev.to/goodidea/building-co-touch-goals-introduction-5bl5</link>
      <guid>https://dev.to/goodidea/building-co-touch-goals-introduction-5bl5</guid>
      <description>&lt;h1&gt;
  
  
  Building Co-Touch: Goals
&lt;/h1&gt;

&lt;p&gt;Today was my first day at &lt;a href="https://www.recurse.com/"&gt;the Recurse Center&lt;/a&gt;, where I'll be spending the next six weeks taking some time off of client work and focusing on learning some new things. My goals for my time here are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;To work with new programming languages for both backend &amp;amp; frontend.&lt;/p&gt;

&lt;p&gt;I have been working as a freelance full-stack developer for the past 7 (almost 8!) years. During that time, I've worked with PHP, Python, Javascript, and a little bit of Ruby - but, Javascript has become my go-to and is what I use for almost all of my work at this point (Last year I made the switch to Typescript, but that's another blog post). On top of that, all of my frontend web work is usually done in React: it's what I know best, and I don't want to jeopardize or slow down a client project by diving into something new.&lt;/p&gt;

&lt;p&gt;So, having the space and time to work on something of my own, I want to branch out.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Publish a cross-platform app.&lt;/p&gt;

&lt;p&gt;Other than some small projects using Cordova, I haven't had the opportunity to build native iOS and Android applications. I want to grow in my career as a developer, and mobile apps have felt like an un-checked box for a few years.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Write more.&lt;/p&gt;

&lt;p&gt;That's what I'm doing here. I never had any formal training in programming; instead, I learned through people who were posting questions, blog posts, tutorials, and classes online. Most of what I have learned from was shared freely. It is amazing to be part of a community that values learning together in this way, and I want to be better at making a contribution.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Form a healthier relationship with work.&lt;/p&gt;

&lt;p&gt;This is a "meta" goal for my time at the Recurse Center. As a freelancer, it is easy to overwork. You don't see coworkers clocking out at a regular time, and if you mostly work alone, it's hard to get a sense of how much work is "enough" for one day. I tend to work compulsively, and too much, and have experienced burnout too many times. I won't be writing about this much in subsequent posts, but this is a big goal for me this year, and I also think it is important that we (as developers, as humans) are open about things we struggle with.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Co-Touch 👉👈
&lt;/h1&gt;

&lt;p&gt;This idea is fairly new, and I'm still not sure of the best way to describe it. But, after meeting a handful of other fresh Recursers this morning and discussing what we will be working on, I've formed a bit of a sound-bite: Co-Touch will be a kind of chatting app, using collaborative drawing instead of messages. I want it to be simple and playful, inspired by apps like &lt;a href="https://flightsimulator.soft.works/"&gt;Flight Simulator&lt;/a&gt; and &lt;a href="https://www.dialup.com/"&gt;Dialup&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The general idea is that you'll share some kind of canvas with another person, and "draw" to each other instead of sending messages. The drawings will be simple, ephemeral, and time-based - like drawing with your finger on a foggy window.&lt;/p&gt;

&lt;p&gt;I don't have any mockups - things are all still very loose, and, since I don't have any collaborators, I think it's OK to just keep it all in my head for now.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Stack
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Mobile Apps&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Right now, the choices I have come to for mobile app development is either React Native or Flutter + Dart. The latter is appealing because I want to learn a new language, but Flutter doesn't include the kind rendering engine I can use to "draw" in the way I am hoping to. But, it is able to interoperate with Javascript, which leads me to:&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Drawing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I've decided to use a web-friendly renderer for the drawing, maybe working directly with HTML5's &lt;code&gt;canvas&lt;/code&gt;, or using a library like &lt;a href="https://p5js.org/"&gt;p5.js&lt;/a&gt;. This part of the project may be the most technically difficult one, so trying to achieve it in a new language might be taking on too much.&lt;/p&gt;

&lt;p&gt;Also, it's important to me that the drawings can exist online, so using javascript seems like the natural choice. While the experience I want to create is primarily a mobile one, I may still want to be able to include some functionality in the browser.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Backend&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I will need some kind of backend &amp;amp; API. Users will need a way to connect with each other somehow, and I want them to be able to save and replay the drawings they have created together. A simple REST API feels like it makes the most sense, but I'll still need to consider:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Real-time collaborative drawing - websockets will be involved, and I'll need a way to stream users' "touches" (or strokes) to others as efficiently as possible.&lt;/li&gt;
&lt;li&gt;Database - Each drawing will be stored as a long list of touches, and not as static images, as I was originally planning. A drawing could potentially be made up of thousands of touches, so I'll need a database that can performantly save new entries and fetch many at a time. I am most familiar with Postgres, but I have also done a little work with &lt;a href="https://dgraph.io/"&gt;DGraph&lt;/a&gt; and might give that a try.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Six weeks is not a lot of time - especially when you are surrounded by dozens of people working on all sorts of interesting projects. We'll see how much of the above I can really get through in my time here. If I end up with a simple MVP, or even just getting the hang of a new programming language, I'll leave happy.&lt;/p&gt;

&lt;p&gt;Stay tuned here on &lt;a href="http://dev.to"&gt;dev.to&lt;/a&gt; for more posts as I continue working. If you have any questions, advice, or feedback, please chime in in the comments, or on twitter at &lt;a href="https://twitter.com/typeof_goodidea"&gt;@typeof_goodidea&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A little bonus: this morning I had a pair programming exercise to build John Conway's &lt;a href="https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life"&gt;Game Of Life&lt;/a&gt; - &lt;a href="https://gist.github.com/good-idea/2648bcba48d3dd26c686f7c54f8391a6"&gt;here is what we came up with&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>learning</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>Developers using Macbooks, have you made the Touch Bar useful?</title>
      <dc:creator>Joseph Thomas</dc:creator>
      <pubDate>Fri, 06 Sep 2019 18:22:19 +0000</pubDate>
      <link>https://dev.to/goodidea/developers-using-macbooks-have-you-made-the-touch-bar-useful-5ep8</link>
      <guid>https://dev.to/goodidea/developers-using-macbooks-have-you-made-the-touch-bar-useful-5ep8</guid>
      <description>&lt;p&gt;I recently upgraded to a newer Macbook, one with a Touch Bar. So far, I haven't found its defaults useful for me in most apps I use for work. I've re-mapped &lt;code&gt;esc&lt;/code&gt; to my Caps Lock key, but otherwise haven't done anything.&lt;/p&gt;

&lt;p&gt;Any way to make this weird thing useful for programming?&lt;/p&gt;

</description>
      <category>discuss</category>
    </item>
    <item>
      <title>What does a healthy and productive work day look like to you?</title>
      <dc:creator>Joseph Thomas</dc:creator>
      <pubDate>Tue, 23 Jul 2019 19:28:31 +0000</pubDate>
      <link>https://dev.to/goodidea/what-does-a-healthy-and-productive-work-day-look-like-to-you-4ajh</link>
      <guid>https://dev.to/goodidea/what-does-a-healthy-and-productive-work-day-look-like-to-you-4ajh</guid>
      <description>&lt;p&gt;I've been freelancing for 7 years, mostly working solo. I recently realized that my own understanding of what constitutes a "productive" day is based only on my own experience --- and, even though I have been able to work much more effectively, I still have a hard time feeling like I have done "a good day's work".&lt;/p&gt;

&lt;p&gt;I think that part of this is that I only feel like I am productive when I'm in a "deep working" state - which often takes me a couple hours of busywork/surfing to warm up to. Then, it's hard to wind down at a regular time because I feel like I should take advantage of being in the zone. Then, I burn out.&lt;/p&gt;

&lt;p&gt;I don't want this discussion to become about "how to be productive" - there are plenty of articles about that (and I think our culture prizes productivity in an unhealthy way anyway).&lt;/p&gt;

&lt;p&gt;Instead, I'm wondering:&lt;/p&gt;

&lt;p&gt;How much of your work day is "deep work"?&lt;/p&gt;

&lt;p&gt;How do you maintain a healthy pace throughout your week?&lt;/p&gt;

&lt;p&gt;How do you maintain a healthy separation between work &amp;amp; the rest of your life?&lt;/p&gt;

&lt;p&gt;Any other thoughts / questions / experiences about this?&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>mentalhealth</category>
      <category>burnout</category>
    </item>
    <item>
      <title>Git help: Merging updates in a classroom repo</title>
      <dc:creator>Joseph Thomas</dc:creator>
      <pubDate>Fri, 31 May 2019 04:15:50 +0000</pubDate>
      <link>https://dev.to/goodidea/git-help-merging-updates-in-a-classroom-repo-1dk8</link>
      <guid>https://dev.to/goodidea/git-help-merging-updates-in-a-classroom-repo-1dk8</guid>
      <description>&lt;p&gt;I have a repo of lessons that I'm using to teach a React class. (&lt;a href="https://git.generalassemb.ly/good-idea/react-exercises"&gt;You can see it here&lt;/a&gt;). &lt;/p&gt;

&lt;p&gt;Each lesson has a few files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a markdown document&lt;/li&gt;
&lt;li&gt;a test file&lt;/li&gt;
&lt;li&gt;an exercise file&lt;/li&gt;
&lt;li&gt;a solution file&lt;/li&gt;
&lt;li&gt;maybe some sample data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The students will work within the exercise file, building out react components to get the tests passing. Most of their changes will be here, but they will also be changing a few lines in the test file (changing &lt;code&gt;it.skip&lt;/code&gt; to &lt;code&gt;it&lt;/code&gt; when they are working on a new test.)&lt;/p&gt;

&lt;p&gt;Everybody has created their own fork of this repo, and have cloned that. I have an &lt;code&gt;init&lt;/code&gt; script that adds the original repo as an &lt;code&gt;upstream&lt;/code&gt; remote.&lt;/p&gt;

&lt;p&gt;I'm making updates to the main repo as we move along: fixing some typos, and cleaning up the notes. I would like to figure out a way for my students to pull in these updates without losing the work that they have done.&lt;/p&gt;

&lt;p&gt;A simple solution is doing &lt;code&gt;git fetch upstream &amp;amp;&amp;amp; git merge --strategy-option ours upstream/master&lt;/code&gt; - which will pull in all new updates, but opt for the student's work whenever there is a merge conflict.&lt;/p&gt;

&lt;p&gt;But, I also want the students to be able to merge in the updates if they choose to do so. One scenario would be if they had been struggling on a particular exercise and wanted to "reset" it.&lt;/p&gt;

&lt;p&gt;An ideal tool would be an interactive CLI where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The student runs the 'update-lessons' script&lt;/li&gt;
&lt;li&gt;For each merge conflict, they can choose to either:

&lt;ul&gt;
&lt;li&gt;Keep their own work&lt;/li&gt;
&lt;li&gt;Back up their own work, and merge in the new updates. For example, if there's a conflict in &lt;code&gt;./src/lesson1/exercise.js&lt;/code&gt;, they could choose this option and end up with:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;./src/lesson1/exercise.js&lt;/code&gt; (The updated file)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;./src/lesson1/exercise.5-30-2019.js&lt;/code&gt; (The work they had already done)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My students are fairly new to git, so I would want to provide them with something simple.&lt;/p&gt;

&lt;p&gt;I don't know git well enough to figure this out, but if I were pointed in the right direction I could write some kind of bash script.&lt;/p&gt;

&lt;p&gt;Does anyone know of any tools, workflows, or git magic that could do something like this?&lt;/p&gt;

</description>
      <category>help</category>
      <category>git</category>
      <category>teaching</category>
    </item>
    <item>
      <title>How to fake AWS locally with LocalStack</title>
      <dc:creator>Joseph Thomas</dc:creator>
      <pubDate>Mon, 11 Mar 2019 18:54:15 +0000</pubDate>
      <link>https://dev.to/goodidea/how-to-fake-aws-locally-with-localstack-27me</link>
      <guid>https://dev.to/goodidea/how-to-fake-aws-locally-with-localstack-27me</guid>
      <description>&lt;p&gt;If you're anything like me, you prefer to avoid logging into the AWS console as much as possible. Did you set up your IAM root user with 2FA and correctly configure the CORS and ACL settings on your S3 bucket?&lt;/p&gt;

&lt;h1&gt;
  
  
  🤷‍♂️ nah.
&lt;/h1&gt;

&lt;p&gt;I also prefer to keep my local development environment as close as possible to how it's going to work in production. Additionally, I'm always looking for new ways to fill up my small hard drive. I can't think of a better away to achieve all of the above than putting a bunch of S3 servers inside my computer.&lt;/p&gt;

&lt;p&gt;This tutorial will cover setting up &lt;a href="https://github.com/localstack/localstack" rel="noopener noreferrer"&gt;Localstack&lt;/a&gt; within a node app. Localstack allows you to emulate a number of AWS services on your computer, but we're just going to use S3 in this example. Also, Localstack isn't specific to Node - so even if you aren't working in Node, a good portion of this tutorial will still be relevant. This also covers a little bit about Docker - if you don't really know what you're doing with Docker or how it works, don't worry. Neither do I.&lt;/p&gt;

&lt;p&gt;You can see the &lt;a href="https://github.com/good-idea/localstack-demo" rel="noopener noreferrer"&gt;demo repo&lt;/a&gt; for the finished code.&lt;/p&gt;

&lt;p&gt;A few benefits of this approach are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You can work offline&lt;/li&gt;
&lt;li&gt;You don't need a shared 'dev' bucket that everyone on your team uses&lt;/li&gt;
&lt;li&gt;You can easily wipe &amp;amp; replace your local buckets&lt;/li&gt;
&lt;li&gt;You don't need to worry about paying for AWS usage&lt;/li&gt;
&lt;li&gt;You don't need to log into AWS 😛&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Initial Setup
&lt;/h2&gt;

&lt;p&gt;First, we'll need to install a few things.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install &lt;a href="https://docs.docker.com/install/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt; if you haven't already.&lt;/li&gt;
&lt;li&gt;Install the &lt;a href="https://aws.amazon.com/cli/" rel="noopener noreferrer"&gt;AWS CLI&lt;/a&gt;. Even though we aren't going to be working with "real" AWS, we'll use this to talk to our local docker containers.&lt;/li&gt;
&lt;li&gt;Once the AWS CLI is installed, run &lt;code&gt;aws configure&lt;/code&gt; to create some credentials. Even though we're talking to our "fake" local service, we still need credentials. You can enter real credentials (as described &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html" rel="noopener noreferrer"&gt;here&lt;/a&gt;), or dummy ones. Localstack requires that these details are present, but doesn't actually validate them. &lt;em&gt;Thanks to &lt;a class="mentioned-user" href="https://dev.to/alexiswilke"&gt;@alexiswilke&lt;/a&gt; for pointing out in the comments that I missed this step!&lt;/em&gt; &lt;/li&gt;
&lt;li&gt;Make a few files. Create a new directory for your project, and within it: &lt;code&gt;touch index.js docker-compose.yml .env &amp;amp;&amp;amp; mkdir .localstack&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Add an image to your project directory and rename it to &lt;code&gt;test-upload.jpg&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm init&lt;/code&gt; to set up a package.json, then &lt;code&gt;npm install aws-sdk dotenv&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Docker
&lt;/h2&gt;

&lt;p&gt;(disclaimer: I'm not a docker expert. If anyone has any suggestions on how to improve or better explain any of this, please let me know in the comments!)&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker Config
&lt;/h3&gt;

&lt;p&gt;You can run Localstack directly from the command line, but I like using Docker because it makes me feel smart. It's also nice because you don't need to worry about installing Localstack on your system. I prefer to use docker-compose to set this up. Here's the config:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt; &lt;/p&gt;

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

version: '3.2'
services:
  localstack:
    image: localstack/localstack:latest
    container_name: localstack_demo
    ports:
      - '4563-4599:4563-4599'
      - '8055:8080'
    environment:
      - SERVICES=s3
      - DEBUG=1
      - DATA_DIR=/tmp/localstack/data
    volumes:
      - './.localstack:/tmp/localstack'
      - '/var/run/docker.sock:/var/run/docker.sock'


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

&lt;/div&gt;

&lt;p&gt;&lt;em&gt;(August 10, 2019 edit: LocalStack now has a larger range of ports, the yaml above has been updated to reflect this. Thanks to @arqez for mentioning this in the comments)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Breaking some of these lines down:&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;image: localstack/localstack:latest&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;Use the latest &lt;a href="https://hub.docker.com/r/localstack/localstack/" rel="noopener noreferrer"&gt;Localstack image from Dockerhub&lt;/a&gt; &lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;container_name: localstack_demo&lt;/code&gt;:
&lt;/h4&gt;

&lt;p&gt;This gives our container a specific name that we can refer to later in the CLI.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;ports: '4563-4599:4563-4599'&lt;/code&gt; and &lt;code&gt;'8055:8080'&lt;/code&gt;:
&lt;/h4&gt;

&lt;p&gt;When your docker container starts, it will open up a few ports. The number on the &lt;strong&gt;left&lt;/strong&gt; binds the port on your &lt;code&gt;localhost&lt;/code&gt; to the port within the container, which is the number on the &lt;strong&gt;right&lt;/strong&gt;. In most cases, these two numbers can be the same, i.e. &lt;code&gt;8080:8080&lt;/code&gt;. I often have some other things running on &lt;code&gt;localhost:8080&lt;/code&gt;, so here, I've changed the default to &lt;code&gt;8055:8080&lt;/code&gt;. This means that when I connect to &lt;code&gt;http://localhost:8055&lt;/code&gt; within my app, it's going to talk to port &lt;code&gt;8080&lt;/code&gt; on the container.&lt;/p&gt;

&lt;p&gt;The line &lt;code&gt;'4563-4584:4563-4584'&lt;/code&gt; does the same thing, but binds a whole range of ports. These particular port numbers are what Localstack uses as endpoints for the various APIs. We'll see more about this in a little bit.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;environment&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;These are environment variables that are supplied to the container. Localstack will use these to set some things up internally:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SERVICES=s3&lt;/code&gt;: You can define a list of AWS services to emulate. In our case, we're just using S3, but you can include additional APIs, i.e. &lt;code&gt;SERVICES=s3,lambda&lt;/code&gt;. There's more on this in the Localstack docs.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DEBUG=1&lt;/code&gt;: 🧻 Show me all of the logs!&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DATA_DIR=/tmp/localstack/data&lt;/code&gt;: This is the directory where Localstack will save its data &lt;em&gt;internally&lt;/em&gt;. More in this next:&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;code&gt;volumes&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;'./.localstack:/tmp/localstack'&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Remember when set up the &lt;code&gt;DATA_DIR&lt;/code&gt; to be &lt;code&gt;/tmp/localstack/data&lt;/code&gt; about 2 seconds ago? Just like the &lt;code&gt;localhost:container&lt;/code&gt; syntax we used on the ports, this allows your containers to access a portion of your hard drive. Your computer's directory on the left, the container's on the right.&lt;/p&gt;

&lt;p&gt;Here, we're telling the container to use our &lt;code&gt;.localstack&lt;/code&gt; directory for its &lt;code&gt;/tmp/localstack&lt;/code&gt;. It's like a symlink, or a magical portal, or something.&lt;/p&gt;

&lt;p&gt;In our case, this makes sure that any data created by the container will still be present once the container restarts. Note that &lt;code&gt;/tmp&lt;/code&gt; is cleared frequently and isn't a good place to store. If you want to put it in a more secure place&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;'/var/run/docker.sock:/var/run/docker.sock'&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Starting our Container
&lt;/h3&gt;

&lt;p&gt;Now that we have our &lt;code&gt;docker-compose.yml&lt;/code&gt; in good shape, we can spin up the container: &lt;code&gt;docker-compose up -d&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To make sure it's working, we can visit &lt;a href="http://localhost:8055" rel="noopener noreferrer"&gt;http://localhost:8055&lt;/a&gt; to see Localstack's web UI. Right now it will look pretty empty:&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhhjjh6q87nlzvprxw6j5.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhhjjh6q87nlzvprxw6j5.png" alt="Empty Localstack UI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Similarly, our S3 endpoint &lt;a href="http://localhost:4572" rel="noopener noreferrer"&gt;http://localhost:4572&lt;/a&gt; will show some basic AWS info:&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fojxiioxxj9y23m9ab74c.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fojxiioxxj9y23m9ab74c.png" alt="Empty S3 bucket"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(If you don't see something similar to these, check the logs for your docker containers)&lt;/p&gt;

&lt;h2&gt;
  
  
  Working with Localstack
&lt;/h2&gt;

&lt;p&gt;AWS is now inside our computer. You might already be feeling a little bit like you are &lt;a href="https://www.businessinsider.com/amazon-ceo-jeff-bezos-richest-person-net-worth-billions-2018-12" rel="noopener noreferrer"&gt;the richest person in the world&lt;/a&gt;. (If not, don't worry, just keep reading 😛)&lt;/p&gt;

&lt;p&gt;Before we start uploading files, we need to create and configure a bucket. We'll do this using the AWS CLI that we installed earlier, using the &lt;code&gt;--endpoint-url&lt;/code&gt; flag to talk to Localstack instead.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a bucket: &lt;code&gt;aws --endpoint-url=http://localhost:4572 s3 mb s3://demo-bucket&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Attach an &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html" rel="noopener noreferrer"&gt;ACL&lt;/a&gt; to the bucket so it is readable: &lt;code&gt;aws --endpoint-url=http://localhost:4572 s3api put-bucket-acl --bucket demo-bucket --acl public-read&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, when we visit the web UI, we will see our bucket:&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fprlobggtwp8fo689ccam.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fprlobggtwp8fo689ccam.png" alt="Localstack UI with S3 Bucket"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you used &lt;code&gt;volumes&lt;/code&gt; in your docker settings, let's pause for a moment to look at what's going on in &lt;code&gt;./.localstack/data&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpeqq1p4ppgtd3sr5ep4k.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpeqq1p4ppgtd3sr5ep4k.png" alt="Localstack S3 JSON"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, we can see that Localstack is recording all API calls in this JSON file. When the container restarts, it will re-apply these calls - this is how we are able to keep our data between restarts. Once we start uploading, we won't see new files appear in this directory. Instead, our uploads will be recorded in this file &lt;em&gt;as raw data&lt;/em&gt;. (You could include this file in your repo if you wanted to share the state of the container with others - but depending on how much you upload, it's going to become a pretty big file)&lt;/p&gt;

&lt;p&gt;If you want to be able to "restore" your bucket later, you can make a backup of this file. When you're ready to restore, just remove the updated &lt;code&gt;s3_api_calls.json&lt;/code&gt; file, replace it with your backup, and restart your container.&lt;/p&gt;

&lt;h3&gt;
  
  
  Uploading from our app
&lt;/h3&gt;

&lt;p&gt;There are a lot of S3 uploading tutorials out there, so this section won't be as in-depth. We'll just make a simple &lt;code&gt;upload&lt;/code&gt; function and try uploading an image a few times.&lt;/p&gt;

&lt;p&gt;Copy these contents into their files:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;.env&lt;/strong&gt;, our environment variables&lt;/p&gt;

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

AWS_ACCESS_KEY_ID='123'
AWS_SECRET_KEY='xyz'
AWS_BUCKET_NAME='demo-bucket'


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

&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Note: it doesn't matter what your AWS key &amp;amp; secret are, as long as they aren't empty.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;aws.js&lt;/strong&gt;, the module for our upload function&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;const&lt;/span&gt; &lt;span class="nx"&gt;AWS&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;aws-sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;dotenv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="na"&gt;accessKeyId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="na"&gt;secretAccessKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_SECRET_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useLocal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NODE_ENV&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bucketName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_BUCKET_NAME&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s3client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;AWS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;S3&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
   &lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="cm"&gt;/**
    * When working locally, we'll use the Localstack endpoints. This is the one for S3.
    * A full list of endpoints for each service can be found in the Localstack docs.
    */&lt;/span&gt;
   &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;useLocal&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://localhost:4572&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="cm"&gt;/**
     * Including this option gets localstack to more closely match the defaults for
     * live S3. If you omit this, you will need to add the bucketName to the `Key`
     * property in the upload function below.
     *
     * see: https://github.com/localstack/localstack/issues/1180
     */&lt;/span&gt;
   &lt;span class="na"&gt;s3ForcePathStyle&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="p"&gt;})&lt;/span&gt;


&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uploadFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
   &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&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="nx"&gt;s3client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
         &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bucketName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&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;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&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="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;
            &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&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="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;uploadFile&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Thanks to &lt;a class="mentioned-user" href="https://dev.to/mcmule"&gt;@mcmule&lt;/a&gt; for the hint about the &lt;code&gt;s3ForcePathStyle&lt;/code&gt; option above. If you're getting an &lt;code&gt;ECONNREFUSED&lt;/code&gt; error, take a look at &lt;a href="https://dev.to/mcmule/comment/f841"&gt;his comment&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;test-upload.js&lt;/strong&gt;, which implements the upload function&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;const&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;const&lt;/span&gt; &lt;span class="nx"&gt;path&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;path&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="nx"&gt;uploadFile&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;./aws&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="nx"&gt;testUpload&lt;/span&gt; &lt;span class="o"&gt;=&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&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-image.jpg&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="nx"&gt;fileStream&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;createReadStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
   &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`test-image-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;.jpg`&lt;/span&gt;
   &lt;span class="nf"&gt;uploadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&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="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="s2"&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;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="nx"&gt;response&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;err&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="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="s2"&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;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="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="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;testUpload&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;the &lt;code&gt;testUpload()&lt;/code&gt; function gets the file contents, gives it a unique name based on the current time, and uploads it. Let's give it a shot:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;node test-upload.js&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkf82tv09fn4grynis6o6.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkf82tv09fn4grynis6o6.png" alt="testing the upload"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Copy the URL in the &lt;code&gt;Location&lt;/code&gt; property of the response and paste it into your browser. The browser will immediately download the image. If you want to see it in your browser, you can use something like JS Bin:&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu4bw6mxrn2nj74v375d4.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu4bw6mxrn2nj74v375d4.png" alt="I love my dog"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, if you look at &lt;code&gt;.localstack/data/s3_api_calls.json&lt;/code&gt; again, you'll see it filled up with the binary data of the image:&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbv583qh5hot5y2jznmsc.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbv583qh5hot5y2jznmsc.png" alt="Image binary data"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Finally&lt;/strong&gt;, let's restart the container to make sure our uploads still work. To do this, run &lt;code&gt;docker restart localstack_demo&lt;/code&gt;. After it has restarted, run &lt;code&gt;docker logs -f localstack_demo&lt;/code&gt;. This will show you the logs of the container (the &lt;code&gt;-f&lt;/code&gt; flag will "follow" them).&lt;/p&gt;

&lt;p&gt;After it initializes Localstack, it will re-apply the API calls found in &lt;code&gt;s3_api_calls.json&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F52o61z1mv1ww3z665yrt.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F52o61z1mv1ww3z665yrt.png" alt="Localstack Logs"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you reload your browser, you should see the image appear just as before.&lt;/p&gt;

&lt;p&gt;🎉 That's it! Thanks for sticking around. This is my first tutorial and I'd love to know what you think. If you have any questions or suggestions, let me know in the comments!&lt;/p&gt;

</description>
      <category>node</category>
      <category>tutorial</category>
      <category>aws</category>
      <category>docker</category>
    </item>
    <item>
      <title>Pusher Contest idea: 🚁Garbage Drones♻️</title>
      <dc:creator>Joseph Thomas</dc:creator>
      <pubDate>Tue, 08 May 2018 21:15:21 +0000</pubDate>
      <link>https://dev.to/goodidea/pusher-contest-idea-garbage-drones-4e74</link>
      <guid>https://dev.to/goodidea/pusher-contest-idea-garbage-drones-4e74</guid>
      <description>&lt;h1&gt;
  
  
  The year is 2020
&lt;/h1&gt;

&lt;p&gt;Jeff Bezos acquires Los Angeles County waste management. All garbage trucks are replaced by drones, which are piloted by Amazon Mechanical Turks. You, a Turker, race against others to pick up the county's garbage.&lt;/p&gt;

&lt;h1&gt;
  
  
  🕹Game design
&lt;/h1&gt;

&lt;p&gt;It's 5am LA time. You and your competitors start up your drones and fly across the city to pick up garbage. At the end of the day, whoever picked up the most garbage wins.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each game is a series of 2-3 minute rounds, representing a full day.&lt;/li&gt;
&lt;li&gt;Players navigate using their arrow keys.&lt;/li&gt;
&lt;li&gt;A scoreboard displays how much garbage each player has picked up.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Base Game
&lt;/h2&gt;

&lt;p&gt;Users visit the website and can opt to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;join an existing game. Games have a max number of players. When you join a game, you enter your username and pick an emoji avatar. (No persistent login at this point). Games have a max number of players. When you join an active game, you are an observer until the next round begins.&lt;/li&gt;
&lt;li&gt;observe a game: A game can have any number of observers&lt;/li&gt;
&lt;li&gt;start a new game&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Play&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;At the beginning of each round, you see the pickup points on the map and have a limited amount of time to select your starting point. You can change this until the game starts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Chat&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Observers and players can send "chat" messages that appear for a few seconds on everybody's GUI. Perhaps active players' chats appear as bubbles above their avatars.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;A lot of this is probably out of scope for the length of the contest, but would be fun to have:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Garbage types&lt;/strong&gt;: Waste, Recycling, and Green garbage. When you start a new round, you pick a drone with different capabilities. Example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single-type drones can only pick up one kind of garbage, but they are the fastest.&lt;/li&gt;
&lt;li&gt;All-purpose drones can pick up anything, but are slower.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Dropoff points&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Drones have different pickup capacities. When they are full, they can't pick up any more garbage. Various dropoff points are located around the city, and must be dropped off before the player gets the 'points' for these pickups. Garbage that hasn't been dropped off at the end of the day doesn't count.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Map View&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the base game, the view of the map will be static. But, having a larger map would make play more interesting. There would be a minimap that showed your location and all pickup points, but you wouldn't know where your competitors were until they were in your view.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Stack
&lt;/h2&gt;

&lt;p&gt;Frontend in React, using the Google Maps API for the base map.&lt;br&gt;
Served with Zeit Now.&lt;br&gt;
Client communication with, of course, Pusher&lt;br&gt;
Backend: TBD! &lt;/p&gt;

</description>
      <category>pushercontest</category>
    </item>
    <item>
      <title>How do you encourage clients or employers to open source?</title>
      <dc:creator>Joseph Thomas</dc:creator>
      <pubDate>Sun, 06 May 2018 18:26:56 +0000</pubDate>
      <link>https://dev.to/goodidea/how-do-you-encourage-clients-or-employers-to-open-source-2pg6</link>
      <guid>https://dev.to/goodidea/how-do-you-encourage-clients-or-employers-to-open-source-2pg6</guid>
      <description>&lt;p&gt;I just got a new job building an MVP for a small startup. We're building an interactive web-based app to be used in K-12 education - something that is fun and engaging for students, and helpful for educators to build in lesson plans and view their students' engagement.&lt;/p&gt;

&lt;p&gt;As a developer, I'd like it to be open-source. I don't expect that it's a project that the community would be contributing to - as our use case is somewhat specific - but, I'd like to be able to keep a public journal of my experience working on the project, and allow others to see how I am approaching problems with a production-scale app. &lt;a href="https://spectrum.chat"&gt;Spectrum&lt;/a&gt; recently &lt;a href="https://github.com/withspectrum/spectrum"&gt;open-sourced their platform&lt;/a&gt; and looking through it has been illuminating.&lt;/p&gt;

&lt;p&gt;In short: I couldn't be where I am now without learning from open source software, and I'd like to find ways to share what I have learned in the context of a "real" production app.&lt;/p&gt;

&lt;p&gt;I think many business owners would err on the side of closed source, the most obvious reason being that they wouldn't want to provide any benefits for competitors.&lt;/p&gt;

&lt;p&gt;🤔&lt;strong&gt;Has your business or the business of your client benefitted from open-source?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;🤔&lt;strong&gt;Have you had this kind of conversation with your client or employer?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;🤔&lt;strong&gt;If you've thought about this for your business, what went into your decision making, and which route did you take?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>business</category>
      <category>discuss</category>
    </item>
    <item>
      <title>How to keep functionality in sync between codebases?</title>
      <dc:creator>Joseph Thomas</dc:creator>
      <pubDate>Sun, 11 Jun 2017 01:10:41 +0000</pubDate>
      <link>https://dev.to/goodidea/how-to-keep-functionality-in-sync-between-codebases</link>
      <guid>https://dev.to/goodidea/how-to-keep-functionality-in-sync-between-codebases</guid>
      <description>&lt;p&gt;I'm building up a new project that will consist of a central API, which a number of different sub-projects will link into.&lt;/p&gt;

&lt;p&gt;For simplicity's sake, let's say that part of the functionality of the whole platform is to get an image's metadata. This might happen in the core API, or on either the server or front-end of one of the sub-projects.&lt;/p&gt;

&lt;p&gt;All of this will be written in Javascript, and functions like this will be pure and contained in their own modules. I'd like to have a single place where I can create and edit these modules, so that in any instance above, I can do something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import getImageMetaData from 'getImageMetadata'; // it's within node_modules

const resource = 'http://example.com/image.jpg'
const metaData = getImageMetaData(resource);

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

&lt;/div&gt;



&lt;p&gt;Is the simplest way simply giving this module its own github repo, then including it in &lt;code&gt;package.json&lt;/code&gt; on all of the different sides of the service, and constantly run (using hooks or whatever) &lt;code&gt;npm install upgrade my-package&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;Any smart ways to keep this all in sync? Preferably as automated as possible ~ there might be up to a dozen of the front-end sub-projects, and I want to be sure that I can fix all of them at once.&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>devops</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
