<?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: Łukasz Wolnik</title>
    <description>The latest articles on DEV Community by Łukasz Wolnik (@limal).</description>
    <link>https://dev.to/limal</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%2F170623%2F199240e8-24d7-4d32-bd5e-20a6c36aa580.png</url>
      <title>DEV Community: Łukasz Wolnik</title>
      <link>https://dev.to/limal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/limal"/>
    <language>en</language>
    <item>
      <title>Reclaiming free disk space from a private Docker repository</title>
      <dc:creator>Łukasz Wolnik</dc:creator>
      <pubDate>Wed, 09 Oct 2024 20:34:47 +0000</pubDate>
      <link>https://dev.to/limal/reclaiming-free-disk-space-from-a-private-docker-repository-30f5</link>
      <guid>https://dev.to/limal/reclaiming-free-disk-space-from-a-private-docker-repository-30f5</guid>
      <description>&lt;p&gt;My private Docker repository just hit 200 GB in size. Below is an instruction on how to reclaim most of that disk space back by deleting old registry's tags.&lt;/p&gt;

&lt;p&gt;First mark tags for deletion based on year. There's a regex that will match all tags from years 2021-2023:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash

# Docker repository details
REGISTRY="https://docker.foo.com"
REPOSITORY="foo-frontend"
USERNAME="username"
PASSWORD="password"

# Get all tags
tags_json=$(curl -sk -u "$USERNAME:$PASSWORD" "$REGISTRY/v2/$REPOSITORY/tags/list")
tags_to_delete=$(echo "$tags_json" | jq -r '.tags[]' | grep -E '^202[123][0-9]{4}$')

# Delete tags
echo "Deleting tags from 2021 and 2022:"
while IFS= read -r tag; do
    echo "Deleting tag: $tag"
    ./delete-tag.sh "$tag" "$REPOSITORY"
    echo "Tag $tag deletion process completed."
done &amp;lt;&amp;lt;&amp;lt; "$tags_to_delete"

echo "All deletion processes completed."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the script &lt;code&gt;delete-tag.sh&lt;/code&gt; that gets digest for a given tag and marks it for deletion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash

if [ $# -eq 0 ]; then
    echo "Error: Please provide a TAG value as an argument."
    exit 1
fi

# Set your registry URL, repository name, and credentials
REGISTRY_URL="https://docker.foo.com"
REPO_NAME="$2"
TAG="$1"
USERNAME="username"
PASSWORD="password"

echo "Received TAG: $TAG"

echo "Fetching manifest for ${REPO_NAME}:${TAG}"
MANIFEST=$(curl -s -k -u "${USERNAME}:${PASSWORD}" \
  -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
  "${REGISTRY_URL}/v2/${REPO_NAME}/manifests/${TAG}")

echo "Debug - Manifest output:"
#echo "$MANIFEST"

if [[ $MANIFEST == *"errors"* ]]; then
  echo "Error fetching manifest:"
  echo "$MANIFEST" | jq .
else
  echo "Manifest found. Extracting digest..."
  CURL_OUTPUT=$(curl -s -k -u "${USERNAME}:${PASSWORD}" \
    -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
    -I "${REGISTRY_URL}/v2/${REPO_NAME}/manifests/${TAG}")

  echo "Debug - Curl output for digest:"
#  echo "$CURL_OUTPUT"

  DIGEST=$(echo "$CURL_OUTPUT" | grep -i Docker-Content-Digest | awk '{print $2}' | tr -d '\r\n')

  echo "Digest: ${DIGEST}"
  echo "Attempting to delete..."

  DELETE_RESPONSE=$(curl -s -i -k -u "${USERNAME}:${PASSWORD}" \
    -H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
    -X DELETE "${REGISTRY_URL}/v2/${REPO_NAME}/manifests/${DIGEST}")

  echo "Delete response:"
#  echo "$DELETE_RESPONSE"

  if [[ $DELETE_RESPONSE == *"202"* ]]; then
    echo "Tag deleted successfully."
  else
    echo "Failed to delete tag. Check the response for more details."
  fi
fi

echo "Script execution completed."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly run &lt;code&gt;docker ps&lt;/code&gt; to identify &lt;code&gt;registry:2&lt;/code&gt; container id and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker exec &amp;lt;container_id&amp;gt; bin/registry garbage-collect /etc/docker/registry/config.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run below to confirm there's now more free space on your disk:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  Getting all repos from a Docker Registry
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -k -u username:password -X GET https://docker.foo.com/v2/_catalog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Getting all tags for a given regsitry
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -k -u username:password -X GET http://docker.foo.com/v2/bar/tags/list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>docker</category>
    </item>
    <item>
      <title>Speed boost to Flipper debugger on Apple M1</title>
      <dc:creator>Łukasz Wolnik</dc:creator>
      <pubDate>Thu, 30 Jun 2022 22:54:50 +0000</pubDate>
      <link>https://dev.to/limal/speed-boost-to-flipper-debugger-on-apple-m1-1gh</link>
      <guid>https://dev.to/limal/speed-boost-to-flipper-debugger-on-apple-m1-1gh</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; Download the Apple silicon builds of Flipper here: &lt;a href="https://github.com/chiragramani/FlipperReleases/releases" rel="noopener noreferrer"&gt;https://github.com/chiragramani/FlipperReleases/releases&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://fbflipper.com/" rel="noopener noreferrer"&gt;Flipper's&lt;/a&gt; official builds are painfully slow on Apple M1 silicon.&lt;/p&gt;

&lt;p&gt;The app was so slow that it hindered my daily work. And the worst bit was that not only my CPU was struggling with the Flipper but it also made an iOS app I was working on painfully slow too!&lt;/p&gt;

&lt;h2&gt;
  
  
  Intel inside
&lt;/h2&gt;

&lt;p&gt;It turned out that Flipper has been running through Rosetta emulator.&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%2F3wak59f5v93alazyfrwn.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%2F3wak59f5v93alazyfrwn.png" alt="Activity Monitor showing Flipper running on Intel binaries"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Universal build to the rescue
&lt;/h2&gt;

&lt;p&gt;Thankfully there are &lt;a href="https://github.com/facebook/flipper/pull/3120" rel="noopener noreferrer"&gt;good&lt;/a&gt; &lt;a href="https://github.com/facebook/flipper/pull/3553" rel="noopener noreferrer"&gt;people&lt;/a&gt; out there that prepared a Universal App build for Flipper that runs natively on M1 silicon to a great success.&lt;/p&gt;

&lt;p&gt;Here's the PR &lt;a href="https://github.com/facebook/flipper/pull/3553" rel="noopener noreferrer"&gt;https://github.com/facebook/flipper/pull/3553&lt;/a&gt; for a reference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;UPDATE:&lt;/strong&gt; the para-official builds are now available at &lt;a href="https://github.com/chiragramani/FlipperReleases/releases" rel="noopener noreferrer"&gt;https://github.com/chiragramani/FlipperReleases/releases&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Build M1 silicon-ready Flipper app by running below on your Terminal:&lt;/p&gt;

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

git clone https://github.com/aarongrider/flipper.git
cd flipper
git checkout m1-universal
cd desktop
yarn
yarn build --mac


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

&lt;/div&gt;

&lt;p&gt;Your Apple binary will be at &lt;code&gt;../dist/mac-universal/Flipper.app&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Run it by double clicking it in the Finder and you'll immediately see that Flipper and most importantly an app you're developing runs much faster now.&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%2Fxr33rhi5vx84tvc2rg8a.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%2Fxr33rhi5vx84tvc2rg8a.png" alt="Activity Monitor showing Flipper running on Apple binaries"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can be happy about your M1 machine again.&lt;/p&gt;

</description>
      <category>apple</category>
      <category>m1</category>
      <category>mobile</category>
      <category>reactnative</category>
    </item>
    <item>
      <title>Basic HTTP authentication in Traefik 2</title>
      <dc:creator>Łukasz Wolnik</dc:creator>
      <pubDate>Mon, 31 May 2021 12:21:38 +0000</pubDate>
      <link>https://dev.to/limal/basic-http-authentication-in-traefik-15l6</link>
      <guid>https://dev.to/limal/basic-http-authentication-in-traefik-15l6</guid>
      <description>&lt;p&gt;In order to secure your website with Basic HTTP authentication in Traefik do the following.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Download &lt;code&gt;apache2-utils&lt;/code&gt;
&lt;/h3&gt;

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

&lt;span class="s"&gt;sudo apt install apache2-utils&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  2. Generate password and copy its output
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="s"&gt;htpasswd -nB adam&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;n&lt;/code&gt; option will display the hash in stdout and the &lt;code&gt;B&lt;/code&gt; option will use more secure encryption.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;devto&lt;/code&gt; password's hash is shown below:&lt;/p&gt;

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

&lt;span class="s"&gt;adam:$2y$05$h9OxLeY20/5uiXjfPgdRxuFlrfqBf2QifYDgrwsR6rAEgX3/dpOGq&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  3. Replace $ with $$
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="s"&gt;adam:$$2y$$05$$h9OxLeY20/5uiXjfPgdRxuFlrfqBf2QifYDgrwsR6rAEgX3/dpOGq&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  4. Create a new middleware in Traefik for HTTP basic auth for your HTTPS entrypoint.
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.middlewares.yourservice-basicauth.basicauth.users=adam:$$2y$$05$$h9OxLeY20/5uiXjfPgdRxuFlrfqBf2QifYDgrwsR6rAEgX3/dpOGq"&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Remember to use the escaped double &lt;code&gt;$&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Create a new chain for your HTTPS connection.
&lt;/h3&gt;

&lt;p&gt;You may have existing rules for your HTTPS route, e.g. compressing, etc.&lt;/p&gt;

&lt;p&gt;So create a new chain where you'll combine your existing middlewars and the newly created &lt;code&gt;yoursite-basicauth&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In your &lt;code&gt;docker-compose.yml&lt;/code&gt; file:&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;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.middlewares.yourservice-https-chain.chain.middlewares=yourservice-basicauth,yourservice-other-middleware-remove-or-replace-with-yours"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traefik.http.routers.yourservice-https.middlewares=yourservice-https-chain"&lt;/span&gt;


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

&lt;/div&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%2Fqbaxl30gdbbdogfrhs9f.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%2Fqbaxl30gdbbdogfrhs9f.png" alt="Basic HTTP auth in Traefik"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Restart your service and your website will now prompt browsers for username and password.&lt;/p&gt;

</description>
      <category>traefik</category>
      <category>docker</category>
      <category>linux</category>
      <category>devops</category>
    </item>
    <item>
      <title>Cerebral Debugger in Next.js</title>
      <dc:creator>Łukasz Wolnik</dc:creator>
      <pubDate>Mon, 29 Mar 2021 18:37:36 +0000</pubDate>
      <link>https://dev.to/limal/cerebral-debugger-in-next-js-3l9n</link>
      <guid>https://dev.to/limal/cerebral-debugger-in-next-js-3l9n</guid>
      <description>&lt;p&gt;If you are getting below error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ReferenceError: WebSocket is not defined
    at Devtools.createSocket
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;when trying to hook &lt;a href="https://cerebraljs.com/docs/introduction/devtools.html"&gt;Cerebral Debugger&lt;/a&gt; up in Next.js try to refactor your bootstrap code from this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;devTools&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="s2"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;devtools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;DevTools&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="s2"&gt;localhost:8585&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;devTools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&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;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="s2"&gt;production&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;devTools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;devtools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;DevTools&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="s2"&gt;localhost:8585&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you're be able to use Cerebral Debugger whilst still using Next.js when calling below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;app&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;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;initialized&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSequence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;initialize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)();&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;sequences&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;providers&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;devTools&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>nextjs</category>
      <category>cerebral</category>
      <category>javascript</category>
      <category>websocket</category>
    </item>
    <item>
      <title>MySQL replication over SSH</title>
      <dc:creator>Łukasz Wolnik</dc:creator>
      <pubDate>Sun, 06 Dec 2020 15:02:41 +0000</pubDate>
      <link>https://dev.to/limal/mysql-replication-over-ssh-3ak5</link>
      <guid>https://dev.to/limal/mysql-replication-over-ssh-3ak5</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F97csov9byisk1792vg32.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F97csov9byisk1792vg32.png" alt="Slave database server is pulling only the latest additions to the Master database over SSH" width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Below is a quick and dirty solution for keeping a database synced with a master copy over SSH that you can set up in 30 minutes on existing databases.&lt;/p&gt;

&lt;p&gt;It's a much easier setup (but unsecure) than &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/replication.html"&gt;a proper real-time MySQL replication one&lt;/a&gt;. Additionally you have full control over timing of syncing as you can use a cronjob to initiate the syncing process.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Below &lt;code&gt;mysql&lt;/code&gt; and &lt;code&gt;mysqldump&lt;/code&gt; commands are optmised to work with MySQL instances running inside a Docker container.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Master setup
&lt;/h1&gt;

&lt;p&gt;Create a file named &lt;code&gt;make-snapshots.sh&lt;/code&gt; with below content in your master server and then make it executable with &lt;code&gt;chmod +x ./make-snapshots.sh&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;MYSQL_PWD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;PasswordMaster1
&lt;span class="c"&gt;# Read the last snapshotted ID&lt;/span&gt;
&lt;span class="nb"&gt;source &lt;/span&gt;last_tablename_id
&lt;span class="c"&gt;# Remove the old snapshot&lt;/span&gt;
&lt;span class="nb"&gt;rm &lt;/span&gt;dump_tablename.sql
&lt;span class="c"&gt;# Write snapshot&lt;/span&gt;
mysqldump &lt;span class="nt"&gt;--protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;TCP &lt;span class="nt"&gt;--ssl-mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;DISABLED &lt;span class="nt"&gt;-u&lt;/span&gt; username &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nt"&gt;--where&lt;/span&gt; &lt;span class="s2"&gt;"id &amp;gt; &lt;/span&gt;&lt;span class="nv"&gt;$LAST_TABLENAME_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; dbname tablename &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; dump_tablename.sql
&lt;span class="c"&gt;# Remove the last snapshot&lt;/span&gt;
&lt;span class="nb"&gt;rm &lt;/span&gt;last_tablename_id
&lt;span class="c"&gt;# Extract and write last ID&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"export LAST_TABLENAME_ID=&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-oP&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\)&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="s2"&gt;(&lt;/span&gt;&lt;span class="se"&gt;\d&lt;/span&gt;&lt;span class="s2"&gt;+)"&lt;/span&gt; dump_tablename.sql | &lt;span class="nb"&gt;tac&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; | &lt;span class="nb"&gt;cut&lt;/span&gt; &lt;span class="nt"&gt;-b&lt;/span&gt; 4-&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; last_tablename_id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before running your first snapshot create &lt;code&gt;last_tablename_id&lt;/code&gt; with below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export LAST_TABLENAME_ID=0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will ensure that the first snapshot will contain all existing data, i.e. as the &lt;code&gt;mysqldump&lt;/code&gt; SQL will equal to &lt;code&gt;WHERE id &amp;gt; 0&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  How does the snapshot bit work
&lt;/h3&gt;

&lt;p&gt;Thanks to the &lt;code&gt;--where&lt;/code&gt; parameter passed to the &lt;code&gt;mysqldump&lt;/code&gt; which tells to backup all rows in a table meeting a criteria. &lt;/p&gt;

&lt;p&gt;After each &lt;code&gt;mysqldump&lt;/code&gt; backup our script is checking what was the last exported ID from a table which is then saved in &lt;code&gt;last_tablename_id&lt;/code&gt; file. Each subsequent runs will read that number and only include rows that have not been exported yet.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;grep -oP "\),\((\d+)&lt;/code&gt; is searching for occurrences of  &lt;code&gt;),(123&lt;/code&gt; string inside the dump. Then &lt;code&gt;tac&lt;/code&gt; is reversing the grep's output, &lt;code&gt;head -1&lt;/code&gt; is taking just the first line and &lt;code&gt;cut&lt;/code&gt; is removing the starting &lt;code&gt;),(&lt;/code&gt; characters leaving the the number of a first column (&lt;strong&gt;which is assumed to be an&lt;/strong&gt; &lt;code&gt;id&lt;/code&gt; - &lt;strong&gt;a primary key&lt;/strong&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Crontab
&lt;/h3&gt;

&lt;p&gt;Add a daily snapshot creation at 2:00 AM by running &lt;code&gt;crontab -e&lt;/code&gt; and adding below row into your crontab.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0 2 * * * cd ~/master &amp;amp;&amp;amp; ./make-snapshots.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Slave setup
&lt;/h1&gt;

&lt;p&gt;On your slave server, i.e. the one that will pull the newest data from the master database, create a file named &lt;code&gt;pull-snapshots.sh&lt;/code&gt; and make it executable with &lt;code&gt;chmod +x&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;MYSQL_PWD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;PasswordSlave1
&lt;span class="c"&gt;# Remove last snapshot&lt;/span&gt;
&lt;span class="nb"&gt;rm &lt;/span&gt;dump_tablename.sql 
&lt;span class="c"&gt;# Pull a snapshot from the master server&lt;/span&gt;
scp username@99.0.0.1:~/master/dump_tablename.sql &lt;span class="nb"&gt;.&lt;/span&gt;
mysql &lt;span class="nt"&gt;--protocol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;TCP &lt;span class="nt"&gt;--ssl-mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;DISABLED &lt;span class="nt"&gt;-u&lt;/span&gt; username dbname &amp;lt; dump_tablename.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For &lt;code&gt;scp&lt;/code&gt; to run without prompting for password you need to &lt;a href="https://unix.stackexchange.com/a/182488"&gt;setup key-based authentication&lt;/a&gt; between the two servers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Crontab
&lt;/h3&gt;

&lt;p&gt;Add below line into your crontab so it runs 30 minutes later than the master's one, e.g. at 2:30 AM.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;30 2 * * * cd ~/your/script/path &amp;amp;&amp;amp; ./pull-snapshots.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Voilà! Your database is now synced with the master!&lt;/p&gt;

&lt;h1&gt;
  
  
  Further improvements:
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;don't rely on &lt;code&gt;grep&lt;/code&gt; as if there wasn't any new entries in a database then the &lt;code&gt;last_tablename_id&lt;/code&gt; will be missing the id, e.g. &lt;code&gt;LAST_TABLENAME_ID=&lt;/code&gt;. It's better to rely on the &lt;code&gt;mysql&lt;/code&gt; query just before running the &lt;code&gt;mysqldump&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;create a special user in the slave's DB that is only authorised for doing &lt;code&gt;INSERT&lt;/code&gt; in your database in case your master server is compromised and someone has injected a harmful SQL into the &lt;code&gt;dump_*.sql&lt;/code&gt; files. You don't want to run &lt;code&gt;DROP DATABASE&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;check for errors in MySQL dump and do not overwrite &lt;code&gt;last_tablename_id&lt;/code&gt; file if there was an error (to keep the last successful exported ID)&lt;/li&gt;
&lt;li&gt;if you need anything more then just use &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/replication.html"&gt;a proper replication setup&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>mysql</category>
      <category>devops</category>
      <category>docker</category>
    </item>
    <item>
      <title>Install rtl88x2bu WiFi adapter drivers on Ubuntu 20.10</title>
      <dc:creator>Łukasz Wolnik</dc:creator>
      <pubDate>Wed, 02 Dec 2020 00:16:46 +0000</pubDate>
      <link>https://dev.to/limal/install-rtl88x2ub-drivers-on-ubuntu-20-10-1ofp</link>
      <guid>https://dev.to/limal/install-rtl88x2ub-drivers-on-ubuntu-20-10-1ofp</guid>
      <description>&lt;p&gt;Ubuntu 20.10 based distros do not support the &lt;strong&gt;rtl88x2bu&lt;/strong&gt; Wifi USB adapter out of the box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Internet connection via Bluetooth
&lt;/h2&gt;

&lt;p&gt;There's a catch 22 when installing a &lt;strong&gt;rtl88x2bu&lt;/strong&gt; WiFi adapter driver. First you need to download it from the Internet but your Linux is not ready to connect to a WiFi.&lt;/p&gt;

&lt;p&gt;Instead of using another PC to download a driver and copy to a USB flash drive simply use Bluetooth tethering on your mobile:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;buy a Bluetooth dongle for your desktop PC&lt;/li&gt;
&lt;li&gt;enable Bluetooth tethering and turn on a hotspot on your mobile&lt;/li&gt;
&lt;li&gt;pair and connect to your mobile phone from your PC&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Installing rtl88x2bu drivers
&lt;/h2&gt;

&lt;p&gt;If you are looking for &lt;strong&gt;rtl88x2bu&lt;/strong&gt; drivers for Linux you have probably already seen the &lt;a href="https://github.com/cilynx/rtl88x2bu"&gt;github.com/cilynx/rtl88x2bu&lt;/a&gt; repo. Unfortunately its build process did not work for me on my Ubuntu 20.10-based system. The source files simply did not compile (both 5.3.1 and 5.8.1).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error! Bad return status for module build on kernel
Consult /var/lib/dkms/rtl88x2bu/5.3.1/build/make.log for more information.

./rtl88x2bu-5.3.1/include/rtw_security.h:252:8: error: redefinition of ‘struct sha256_state’
  252 | struct sha256_state {
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I have found another solution in the following repo instead: &lt;a href="https://github.com/morrownr/88x2bu"&gt;github.com/morrownr/88x2bu&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Below are instructions for Ubuntu taken from that repo's README:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt-get update
git clone https://github.com/morrownr/88x2bu.git
cd 88x2bu
sudo ./install-driver.sh
sudo reboot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>linux</category>
      <category>ubuntu</category>
    </item>
    <item>
      <title>node-sass considered harmful</title>
      <dc:creator>Łukasz Wolnik</dc:creator>
      <pubDate>Wed, 04 Nov 2020 17:52:00 +0000</pubDate>
      <link>https://dev.to/limal/node-sass-considered-harmful-4fjb</link>
      <guid>https://dev.to/limal/node-sass-considered-harmful-4fjb</guid>
      <description>&lt;p&gt;... to your developer and Continuous Integration experience due to its painfully slow installation process.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.npmjs.com/package/node-sass"&gt;node-sass&lt;/a&gt; during its installation &lt;code&gt;npm install node-sass&lt;/code&gt; will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Trigger the &lt;code&gt;node-gyp&lt;/code&gt; if it won't be able to find binaries for your operating system.&lt;/li&gt;
&lt;li&gt;Use Python 2.7 to run some scripts.&lt;/li&gt;
&lt;li&gt;Run a painfully slow &lt;code&gt;postinstall&lt;/code&gt; process (adds up to 1 minute to your CI build time).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So if your team is using various operating systems (Windows/Linux/MacOS) or using a Continuous Integration system just replace it with a much better alternative &lt;a href="https://www.npmjs.com/package/sass"&gt;sass&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/sass"&gt;sass&lt;/a&gt; is just a JavaScript package and has zero external dependencies.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create React App
&lt;/h3&gt;

&lt;p&gt;I guess the reason why &lt;code&gt;node-sass&lt;/code&gt; is still so popular is due to the &lt;a href="https://create-react-app.dev/docs/adding-a-sass-stylesheet"&gt;Create React App support for SASS&lt;/a&gt;. But you don't have to eject a CRA if you don't want to compromise your installation process or a build time. Simply use below command inside your app.&lt;/p&gt;

&lt;h1&gt;
  
  
  TL;DR
&lt;/h1&gt;

&lt;p&gt;Transparently replace your &lt;code&gt;node-sass&lt;/code&gt; with &lt;code&gt;sass&lt;/code&gt; and enjoy faster builds and quicker installs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rm -rf node_modules
npm install node-sass@npm:sass -D --legacy-peer-deps
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>react</category>
      <category>css</category>
      <category>node</category>
      <category>devops</category>
    </item>
    <item>
      <title>Simplify your monorepo with npm 7 workspaces</title>
      <dc:creator>Łukasz Wolnik</dc:creator>
      <pubDate>Sat, 31 Oct 2020 00:56:39 +0000</pubDate>
      <link>https://dev.to/limal/simplify-your-monorepo-with-npm-7-workspaces-5gmj</link>
      <guid>https://dev.to/limal/simplify-your-monorepo-with-npm-7-workspaces-5gmj</guid>
      <description>&lt;p&gt;&lt;a href="https://monorepo-anim.wolnik.co.uk/" rel="noopener noreferrer"&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%2Fi%2F52bowb84cy732teck6ky.gif" alt="Hot reloading for UI React component" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This month npm has &lt;a href="https://blog.npmjs.org/post/631877012766785536/release-v700" rel="noopener noreferrer"&gt;released a major version&lt;/a&gt; of their package manager &lt;strong&gt;npm 7&lt;/strong&gt;. It shipped with support for workspaces.&lt;/p&gt;

&lt;p&gt;Why is it big news? Because npm is the only package manager that comes bundled with every NodeJS. To use &lt;em&gt;yarn&lt;/em&gt; or &lt;em&gt;pnpm&lt;/em&gt; you have to take an additional step and install them first.&lt;/p&gt;

&lt;p&gt;Read on and you'll find out how to use npm 7 workspaces in a real-world scenario and learn that using workspaces the npm's way is very different to yarn's.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monorepo use cases
&lt;/h2&gt;

&lt;p&gt;A monorepo is a term describing a single git repository that contains many projects.&lt;/p&gt;

&lt;p&gt;The most common reason to set up a monorepo is to streamline work within a dev team that maintains multiple apps that are using a shared piece of code, for example a common User Interface library.&lt;/p&gt;

&lt;p&gt;Imagine a team that develops two React apps that shares some common UI elements like inputs, selectors, accordions, etc. It would be nice to extract that UI in form of React components and prepare building blocks that are ready to use for all members of the team.&lt;/p&gt;

&lt;p&gt;Apart from that it's just more convenient to have all your source files opened in a single IDE instance. You can jump from project to project without switching windows on your desktop.&lt;/p&gt;

&lt;h2&gt;
  
  
  I just want that nice button in my app too
&lt;/h2&gt;

&lt;p&gt;Let's say I want to build two independent React apps called &lt;code&gt;app1&lt;/code&gt; and &lt;code&gt;app2&lt;/code&gt; that will use a common component from a common UI library called &lt;code&gt;ui&lt;/code&gt;. And I want both apps to hot reload whenever I edit a file inside the UI library.&lt;/p&gt;

&lt;p&gt;By independent I mean that &lt;code&gt;app1&lt;/code&gt; doesn't know anything about &lt;code&gt;app2&lt;/code&gt; and vice-versa.&lt;/p&gt;

&lt;p&gt;Below is a setup that is compatible with npm 7 workspaces.&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%2Fi%2Fhcf8f0qc0s6gzin0fic5.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%2Fi%2Fhcf8f0qc0s6gzin0fic5.png" alt="Monorepo structure" width="800" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining workspaces in npm 7
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;I am prefixing all packages' names with &lt;a class="mentioned-user" href="https://dev.to/xyz"&gt;@xyz&lt;/a&gt; so they will be distinctive from the official npm registry. Just change it to yours.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the most crucial part of the whole setup. Insert below inside your root folder's &lt;code&gt;package.json&lt;/code&gt; to set up a monorepo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@xyz/monorepo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"private"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"workspaces"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"./common/*"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The new &lt;code&gt;"workspaces"&lt;/code&gt; property lets npm know that I want to track any packages inside &lt;code&gt;./common&lt;/code&gt; folder and automatically symlink them in the root's &lt;code&gt;node_modules&lt;/code&gt; when I run &lt;code&gt;npm install&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;From now on whenever our React apps will use &lt;code&gt;import Foo from "@xyz/ui"&lt;/code&gt; the NodeJS will find it in &lt;code&gt;./node_modules/common/@xyz/ui&lt;/code&gt; that points to &lt;code&gt;./common/ui&lt;/code&gt; folder that contains our library. Perfect! No need for &lt;code&gt;npm link&lt;/code&gt; anymore with the workspaces.&lt;/p&gt;

&lt;p&gt;Without workspaces the React app would complain that it cannot find a module named &lt;code&gt;@xyz/ui&lt;/code&gt; and would start looking for it in the npm official registry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Take the first step
&lt;/h2&gt;

&lt;p&gt;To test our setup let's share a text from the &lt;code&gt;ui&lt;/code&gt; library and import that string into our React app.&lt;/p&gt;

&lt;p&gt;Create the common UI library's &lt;code&gt;package.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@xyz/ui"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"private"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"index.js"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and &lt;code&gt;index.js&lt;/code&gt; file that will export a string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;This comes from UI! 1.0.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Time to import that string into our apps.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To quickly create React apps inside our monorepo. Just use the newly released Create React App 4:&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;apps
&lt;span class="nb"&gt;cd &lt;/span&gt;apps
npx create-react-app app1
npx create-react-app app2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you'd think that we need to add our &lt;code&gt;ui&lt;/code&gt; library to the app. In yarn it would look like below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;yarn workspace app1 add @xyz/ui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But with npm we don't need to add any dependency at all.&lt;/p&gt;

&lt;p&gt;Just go to your &lt;code&gt;App.js&lt;/code&gt; file in both &lt;strong&gt;app1&lt;/strong&gt; and &lt;em&gt;app2&lt;/em&gt; apps and add the below code to display a string from our UI library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;testString&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@xyz/ui&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;span&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;testString&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/span&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To test it use the below commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# create a symlink to the @xyz/ui in the root folder
npm install
# go to the app's folder
cd apps/app1
# For CRA 4 you may need to add SKIP_PREFLIGHT_CHECK=true to .env file
# And use the --legacy-peer-deps flag as many packages hasn't been updated yet to officially support React 17
npm install --legacy-peer-deps
npm run start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and from another terminal window:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd apps/app2
npm install
npm run start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see the &lt;code&gt;This comes from UI! 1.0.0&lt;/code&gt; text rendered inside both of your React apps!&lt;/p&gt;

&lt;h2&gt;
  
  
  Export React JSX components
&lt;/h2&gt;

&lt;p&gt;Now if you'd try to export a JSX component the React apps will complain that they cannot parse the JSX. You need to transpile JSX code from the common UI package first.&lt;/p&gt;

&lt;p&gt;You can use a basic Webpack 5 setup:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;common/ui/package.json&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@xyz/ui"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"private"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"build/ui.bundle.min.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Changed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;main&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;module&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"webpack --config webpack.prod.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"build-watch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"webpack --config webpack.prod.js --watch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;webpack&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;dependencies&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;common/ui/babel.config.js&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="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;presets&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@babel/preset-react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;targets&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&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;current&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="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;&lt;code&gt;common/ui/webpack.prod.js&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;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="s2"&gt;path&lt;/span&gt;&lt;span class="dl"&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="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;import&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./src/index.js&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;module&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;rules&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;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;jsx&lt;/span&gt;&lt;span class="se"&gt;?&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;exclude&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/node_modules/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;loader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;babel-loader&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ui.bundle.min.js&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="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="s2"&gt;build&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="c1"&gt;// Below two important lines!&lt;/span&gt;
    &lt;span class="na"&gt;library&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;xyzUI&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;libraryTarget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;umd&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our simple component:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;common/ui/src/index.js&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&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;UIExample&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Shared&lt;/span&gt; &lt;span class="nx"&gt;UI&lt;/span&gt; &lt;span class="nx"&gt;library&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/h1&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;UIExample&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Import the &lt;code&gt;UIExample&lt;/code&gt; component inside your React app using below:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;apps/app1/src/App.js&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;UIExample&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@xyz/ui&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;UIExample&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;from app1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;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 the UI library is transpiled on every code change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd common/ui
npm run build-watch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the app1 in a separate terminal window and take notice that whenever you edit the UI component the webpack dev server will automatically reload it with the newest version thanks to the webpack watch running in the background.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd apps/app1
npm run start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Below I'm editing the common UI component &lt;code&gt;UIElement&lt;/code&gt; and upon saving both React apps are automatically refreshed with the updated component:&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%2Fi%2Ftbu8b7hujyin5e1h2uip.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ftbu8b7hujyin5e1h2uip.gif" alt="Hot reload" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;With the newest npm 7 and its support of workspaces it is now possible to have a monorepo without a need of any external tools like &lt;code&gt;@react-workspaces&lt;/code&gt; or &lt;code&gt;nx&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Just remember that npm has a different philosophy than yarn. For example you cannot run a script inside a workspace from the monorepo's root folder.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Recognize also that &lt;code&gt;@xyz/app1&lt;/code&gt; and &lt;code&gt;@xyz/app2&lt;/code&gt; weren't defined in the monorepo's package.json &lt;code&gt;workspaces&lt;/code&gt; property. Only the modules that will be exported need to be there (&lt;code&gt;@xyz/ui&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The npm 7 workspaces are mainly providing discovery for the modules.&lt;/strong&gt; I wish it had been emphasised in release notes and that the npm's help examples were a bit more complex. I hope this article fills this gap for the time being.&lt;/p&gt;

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

&lt;p&gt;Check out my &lt;a href="https://github.com/limal/gif-css-animation-monorepo" rel="noopener noreferrer"&gt;gif-css-animation-monorepo&lt;/a&gt; repository that shows how I made the animation for this article using &lt;a href="https://monorepo-anim.wolnik.co.uk/" rel="noopener noreferrer"&gt;an HTML page&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>react</category>
      <category>npm</category>
      <category>devops</category>
      <category>monorepo</category>
    </item>
    <item>
      <title>Self-hosted Continuous Delivery that doesn't cost a fortune 💰 - Part 2</title>
      <dc:creator>Łukasz Wolnik</dc:creator>
      <pubDate>Sat, 25 Jan 2020 01:05:49 +0000</pubDate>
      <link>https://dev.to/limal/self-hosted-continuous-delivery-that-doesn-t-cost-a-fortune-part-2-3m1m</link>
      <guid>https://dev.to/limal/self-hosted-continuous-delivery-that-doesn-t-cost-a-fortune-part-2-3m1m</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/limal/how-to-set-up-a-continuous-delivery-with-traefik-1lgg"&gt;the first part&lt;/a&gt; we set up Docker and installed Traefik on our VPS (Virtual Private Server).&lt;/p&gt;

&lt;p&gt;What's left in this final part of setting up your own Continuous Delivery pipeline is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;setting up your web app&lt;/li&gt;
&lt;li&gt;installing Drone&lt;/li&gt;
&lt;li&gt;connecting GitHub to Drone&lt;/li&gt;
&lt;li&gt;setting up an auto-update over SSH&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Setting up a new web app in Traefik
&lt;/h2&gt;

&lt;p&gt;I think it was worth to install Traefik just for being able to very quickly deploy any Docker image and link a (sub)domain to it with Let's Encrypt's SSL.&lt;/p&gt;

&lt;p&gt;It allows you to add a new NodeJS/React/Wordpress web apps or MySQL/PostgreSQL instances in a matter of minutes.&lt;/p&gt;

&lt;p&gt;Because once Traefik is set up adding a new piece of software boils down to setting up a new A type in a DNS zone for your domain that will point to your server's IP address and adding another entry in &lt;code&gt;docker-compose.yml&lt;/code&gt; file in &lt;code&gt;sites&lt;/code&gt; directory. It's quite close to an expierience with using Netlify.&lt;/p&gt;

&lt;p&gt;To start, create a new directory inside your home directory called &lt;code&gt;sites&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;cd ~
mkdir sites
cd sites
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;docker-compose.yml&lt;/code&gt; file and paste below into it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: '3'

services:
  fooapp: # **** EDIT HERE ****
    image: docker.wolnik.co.uk/fooapp # **** EDIT HERE ****
    labels:
      - traefik.backend=fooapp # **** EDIT HERE ****
      - traefik.frontend.rule=Host:fooapp.com # **** EDIT HERE ****
      - traefik.docker.network=web
      - traefik.port=80
    networks:
      - web
  mysql:
    image: mysql:8.0.3
    restart: always
    ports:
      - 3306:3306
    environment:
      MYSQL_ROOT_PASSWORD: apasswordheretochange # **** EDIT HERE ****
      MYSQL_DATABASE: yourdbname # **** EDIT HERE ****
    networks:
      - web
  adminer:
    image: dockette/adminer
    labels:
      - traefik.frontend.rule=Host:mysql.foo.com # **** EDIT HERE ****
      - traefik.docker.network=web
      - traefik.port=80
    restart: always
    ports:
      - 8080:80 # Here I'm mapping adminer's default 8080 port to 80
    networks:
      - web

networks:
  web:
    external: true

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

&lt;/div&gt;



&lt;p&gt;There's quite a lot going on above but actually the only new part that deals with Traefik is adding labels so that Traefik can assign a domain for your container and route to the right port.&lt;/p&gt;

&lt;p&gt;Run your containers with (you must be in &lt;code&gt;~/sites&lt;/code&gt; directory):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose up -d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-d&lt;/code&gt; flag means that &lt;code&gt;docker-compose&lt;/code&gt; will run in background so you can exit your SSH session and your containers will still run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing Drone
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Setting up GitHub OAuth App
&lt;/h3&gt;

&lt;p&gt;Go to &lt;a href="https://github.com"&gt;GitHub&lt;/a&gt;. Open &lt;strong&gt;Settings → Developer options  → OAuth Apps&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PXzW2Teg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/9uhosh1vnomwnxsb516m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PXzW2Teg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/9uhosh1vnomwnxsb516m.png" alt="Alt Text" width="800" height="228"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then fill out the fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Homepage URL: &lt;strong&gt;&lt;a href="https://drone.yourdomain.com"&gt;https://drone.yourdomain.com&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Authorization callback URL: &lt;strong&gt;&lt;a href="https://drone.yourdomain.com/login"&gt;https://drone.yourdomain.com/login&lt;/a&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Us1RNVzS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/axofkgacui9zdt8uzffb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Us1RNVzS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/axofkgacui9zdt8uzffb.png" alt="Alt Text" width="630" height="579"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once created copy the GitHub client id and secret:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aZFD9l0P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/cdcpu26klmf3jo3c7x0m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aZFD9l0P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/cdcpu26klmf3jo3c7x0m.png" alt="Alt Text" width="691" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Drone Server
&lt;/h3&gt;

&lt;p&gt;Create a subfolder in your home folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd ~ &amp;amp;&amp;amp; mkdir drone &amp;amp;&amp;amp; cd $_
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;docker-compose.yml&lt;/code&gt; file and paste into it below:&lt;br&gt;
&lt;/p&gt;

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

networks:
  web:
    external: true
  internal:
    external: false

services:
  drone:
    image: drone/drone:1
    labels:
      - traefik.backend=drone
      - traefik.frontend.rule=Host:drone.yourdomain.com # **** EDIT HERE ****
      - traefik.docker.network=web
      - traefik.port=80
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /var/lib/drone:/home/yourusername/traefik/drone/data # **** EDIT HERE ****
    environment:
      - DRONE_GITHUB_SERVER=https://github.com
      - DRONE_GITHUB_CLIENT_ID=********** # **** EDIT HERE ****
      - DRONE_GITHUB_CLIENT_SECRET=**************************** # **** EDIT HERE ****
      - DRONE_RUNNER_CAPACITY=2
      - DRONE_RPC_SECRET=THIS_RANDOIM_STRING_NEED_TO_BE_THE_SAME # **** EDIT HERE ****
      - DRONE_SERVER_HOST=drone.yourdomain.com # **** EDIT HERE ****
      - DRONE_SERVER_PROTO=https
      - DRONE_TLS_AUTOCERT=true
      - DRONE_AGENTS_ENABLED=true
    networks:
      - internal
      - web
  runner:
    image: drone/drone-runner-docker:1
    restart: always
    ports:
      - "3000:3000"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - DRONE_RPC_PROTO=https
      - DRONE_RPC_HOST=drone.yourdomain.com # **** EDIT HERE ****
      - DRONE_RPC_SECRET=THIS_RANDOM_STRING_NEED_TO_BE_THE_SAME # **** EDIT HERE ****
      - DRONE_RUNNER_CAPACITY=2
      - DRONE_RUNNER_NAME=drone.yourdomain.com THIS_STRING_NEED_TO_BE_THE_SAME # **** EDIT HERE ****
    networks:
      - internal
      - web


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

&lt;/div&gt;



&lt;p&gt;Go to &lt;a href="https://drone.yourdomain.com"&gt;drone.yourdomain.com&lt;/a&gt; URL and you should see Drone UI running. Asking you to login via your GitHub.&lt;/p&gt;

&lt;h2&gt;
  
  
  Login to Docker on VPS
&lt;/h2&gt;

&lt;p&gt;Make sure your VPS can connect to your Docker Hub (or your own Docker repo) by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker login -u user
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or if you have your own Docker repo at &lt;strong&gt;docker.yourdomain.com&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker login -u user docker.yourdomain.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can confirm that you are logged in if there's an &lt;strong&gt;auth&lt;/strong&gt; section in &lt;code&gt;~/.docker/config.json&lt;/code&gt; file. And of course if you are not asked for authentication when pulling image from Docker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Continuous Delivery in Drone
&lt;/h2&gt;

&lt;p&gt;First create a script on your VPS that pulls Docker images and restarts &lt;code&gt;docker-compose.yml&lt;/code&gt; in &lt;code&gt;~/sites&lt;/code&gt; directory. Name it &lt;code&gt;update-all.sh&lt;/code&gt; and paste below into it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker pull docker.wolnik.co.uk/fooapp # **** EDIT **** Docker image name
docker pull docker.yourdomain.com/fooapp # **** EDIT/DELETE **** Another web app
docker-compose up -d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Insert there all images that you are intending to update over Drone. So that &lt;code&gt;docker-compose&lt;/code&gt; will use the latest Docker images of your web apps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Drone SSH user
&lt;/h3&gt;

&lt;p&gt;Above &lt;code&gt;update-all.sh&lt;/code&gt; script must be invoked by Drone runner after completing building and pushing new Docker images once they are build.&lt;/p&gt;

&lt;p&gt;The best way is to create a separate user for this task on your VPS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;adduser drone
sudo usermod -aG docker drone
chown drone: ~/sites/update-all.sh
chmod +x ~/sites/update-all.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  .drone.yml
&lt;/h3&gt;

&lt;p&gt;In order for Drone to know how to build your web app add &lt;code&gt;.drone.yml&lt;/code&gt; file inside root of your Git repo, i.e.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xHqnCzuL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/6s50tsio9rl9qtamwb4h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xHqnCzuL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/6s50tsio9rl9qtamwb4h.png" alt="Alt Text" width="730" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Paste below into &lt;code&gt;.drone.yml&lt;/code&gt; file inside your Git repo, add and commit to your &lt;code&gt;master&lt;/code&gt; branch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pipeline:
  build:
    image: plugins/docker
    registry: docker.wolnik.co.uk
    repo: docker.wolnik.co.uk/fooapp
    username:
      from_secret: docker_username
    password:
      from_secret: docker_password
    tags:
      - 1.0.0
      - 1.0
      - latest
  ssh:
    image: appleboy/drone-ssh
    host: fooapp.com
    username:
      from_secret: ssh_username
    password:
      from_secret: ssh_password
    port: 22
    command_timeout: 2m
    script:
      - cd /home/yourdominaname/sites/ &amp;amp;&amp;amp; ./update-all.sh

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

&lt;/div&gt;



&lt;p&gt;In above file we set up two steps that will build and deploy our web app.&lt;/p&gt;

&lt;p&gt;First step - &lt;strong&gt;build&lt;/strong&gt; - will pull the latest Git repo from &lt;code&gt;master&lt;/code&gt; and build Docker image based on its &lt;code&gt;Dockerfile&lt;/code&gt;. Then it will push the image to your Docker registry.&lt;/p&gt;

&lt;p&gt;Second step - &lt;strong&gt;ssh&lt;/strong&gt; - will connect to your server over SSH, go to your sites directory and run &lt;code&gt;update-all.sh&lt;/code&gt; script which in turn will pull your Docker images and restart &lt;code&gt;docker-compose&lt;/code&gt; so that your web app is using the latest - just built - Docker image.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important if you are using your own Docker repo!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Remember that both &lt;code&gt;registry&lt;/code&gt; URL and &lt;code&gt;repo&lt;/code&gt; must match, i.e. repo can't be just &lt;code&gt;fooapp&lt;/code&gt; if registry has a domain &lt;code&gt;docker.yourdomain.com&lt;/code&gt;. It needs to be then &lt;code&gt;docker.yourdomain.com/fooapp&lt;/code&gt; in order to work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up secrets in Drone
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;.drone.yml&lt;/code&gt; contains four secret strings that forms two credentials. One for Docker repo (DockerHub or your own Docker repo) and SSH access.&lt;/p&gt;

&lt;p&gt;Go to your Drone UI and set up 4 secrets that will replace the placeholders in your Git repo's &lt;code&gt;.drone.yml&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rzGJb-YG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/v0tchomwb7pdx58ubleb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rzGJb-YG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/v0tchomwb7pdx58ubleb.png" alt="Alt Text" width="786" height="950"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Please be patient while fine tuning your &lt;code&gt;.drone.yml&lt;/code&gt; and Drone secrets. Sometimes it's tricky to get the Drone to pick up the lastest GitHub's commit.&lt;/p&gt;

&lt;p&gt;But it's all worth it once you can see a running gear like below for your own repo:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dEB62MJ1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/3x7nzn968on80i4ccpfc.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dEB62MJ1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://thepracticaldev.s3.amazonaws.com/i/3x7nzn968on80i4ccpfc.gif" alt="Alt Text" width="600" height="263"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;P.S. Of course this is just a start. Now you can extend your Drone's pipeline with additional step like running tests before deploying anything on a live site.&lt;/p&gt;

</description>
      <category>drone</category>
      <category>traefik</category>
      <category>docker</category>
      <category>continuousdelivery</category>
    </item>
    <item>
      <title>Self-hosted Continuous Delivery that doesn't cost a fortune 💰 - Part 1</title>
      <dc:creator>Łukasz Wolnik</dc:creator>
      <pubDate>Fri, 24 Jan 2020 21:54:05 +0000</pubDate>
      <link>https://dev.to/limal/how-to-set-up-a-continuous-delivery-with-traefik-1lgg</link>
      <guid>https://dev.to/limal/how-to-set-up-a-continuous-delivery-with-traefik-1lgg</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;In this series I want to demonstrate how to set up an open-sourced, self-managed continuous delivery solution that can be run on a private server for free thanks to &lt;a href="https://drone.io/" rel="noopener noreferrer"&gt;Drone&lt;/a&gt; and &lt;a href="https://traefik.io" rel="noopener noreferrer"&gt;Traefik&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The goal is to update a web app on a live website by just running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git push origin master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F98hrl7iz1026xylxrm80.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F98hrl7iz1026xylxrm80.gif" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;on your dev machine.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fj2ona0p6fousamyk9u7f.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fj2ona0p6fousamyk9u7f.gif" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it! After a couple of minutes a website should be automatically updated with changes that have just been pushed to a Git repo.&lt;/p&gt;

&lt;p&gt;Below image depicts this process from pushing a commit to serving an HTTPS website.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fvcd51fgahv9uuhfb1qpa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fvcd51fgahv9uuhfb1qpa.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;You need to have a Linux server to host and run the required software. You can buy one for example from &lt;a href="https://www.ovh.co.uk/vps/" rel="noopener noreferrer"&gt;OVH&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tech stack
&lt;/h2&gt;

&lt;p&gt;The presented continuous delivery's pipeline contains:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;a href="https://github.com/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;A hosted Git repo with hooks that notifies a build server about a new commit.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;a href="https://drone.io/" rel="noopener noreferrer"&gt;Drone&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;A build server that will listen to new commits, build images and push them to a Docker repo.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;a href="https://traefik.io" rel="noopener noreferrer"&gt;Traefik&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;A load balancer and a reverse proxy, that runs a Docker's image of a web app and routes a web domain &lt;a href="https://foo.bar.com" rel="noopener noreferrer"&gt;foo.bar.com&lt;/a&gt; to a container that runs a Docker image build with Drone.&lt;/p&gt;

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

&lt;p&gt;To start we need a Docker running on a server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt-get update
sudo apt install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable"
sudo apt update
apt-cache policy docker-ce
sudo apt install docker-ce
sudo systemctl status docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Press &lt;code&gt;Ctrl-C&lt;/code&gt; to quit.&lt;/p&gt;

&lt;p&gt;Below will allow your non-root user to access Docker.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo usermod -aG docker yourusername
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Installing &lt;a href="https://docs.docker.com/compose/" rel="noopener noreferrer"&gt;docker-compose&lt;/a&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo curl -L https://github.com/docker/compose/releases/download/1.21.2/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Installing Traefik
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;For more detailed installation refer to the article I based this one on &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-use-traefik-as-a-reverse-proxy-for-docker-containers-on-ubuntu-18-04" rel="noopener noreferrer"&gt;[1]&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Go to your home directory and create a folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd ~
mkdir traefik
cd traefik
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set up a password for your Traefik dashboard using &lt;code&gt;apache2-utils&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;sudo apt-get install apache2-utils
htpasswd -Bc password.txt yourusername
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;password.txt&lt;/code&gt; and copy your password hash from there. You will need it in the next step.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;traefik.toml&lt;/code&gt; file and paste below content into it:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;defaultEntryPoints = ["http", "https"]

[entryPoints]
  [entryPoints.dashboard]
    address = ":8080"
    [entryPoints.dashboard.auth]
      [entryPoints.dashboard.auth.basic]
        users = ["$output_of_password.txt_file_created_above"] # ***** EDIT HERE *****
  [entryPoints.http]
    address = ":80"
      [entryPoints.http.redirect]
        entryPoint = "https"
  [entryPoints.https]
    address = ":443"
      [entryPoints.https.tls]

[api]
entrypoint="dashboard"

[acme]
email = "youremail@email.com" # ***** EDIT HERE *****
storage = "acme.json"
entryPoint = "https"
onHostRule = true
  [acme.httpChallenge]
  entryPoint = "http"

[docker]
domain = "docker.yourdomain.com" # ***** EDIT HERE *****
watch = true
network = "web"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo docker network create web
touch acme.json
chmod 600 acme.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run Traefik:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vim docker-compose.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And paste below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;version: '3'

services:
  traefik:
    image: traefik:1.7.6-alpine
    restart: always
    ports:
      - "80:80"
      - "443:443"
    labels:
      - traefik.frontend.rule=Host:traefik.foo.bar.com # ***** EDIT HERE *****
      - traefik.port=8080
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik.toml:/traefik.toml
      - ./acme.json:/acme.json
    networks:
      - web

networks:
  web:
    external: true
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run Traefik&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker-compose up -d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Traefik Dashboard
&lt;/h3&gt;

&lt;p&gt;To confirm that your Traefik installation was successful you should be able to access &lt;code&gt;traefik.foo.bar.com&lt;/code&gt; URL that you specified in above &lt;code&gt;docker-compose.yml&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;You should see a website like below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F3un4uwoapefrd5vc66sf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F3un4uwoapefrd5vc66sf.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Well done! You've got half of your continuous delivery pipeline set up. In the next article I will show how to install Drone build server and how to connect the trio: GitHub, Drone and Traefik.&lt;/p&gt;

&lt;p&gt;[1] &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-use-traefik-as-a-reverse-proxy-for-docker-containers-on-ubuntu-18-04" rel="noopener noreferrer"&gt;https://www.digitalocean.com/community/tutorials/how-to-use-traefik-as-a-reverse-proxy-for-docker-containers-on-ubuntu-18-04&lt;/a&gt;&lt;/p&gt;

</description>
      <category>drone</category>
      <category>traefik</category>
      <category>docker</category>
      <category>continuousdelivery</category>
    </item>
    <item>
      <title>Using Raspberry Pi 4 with a power adapter only</title>
      <dc:creator>Łukasz Wolnik</dc:creator>
      <pubDate>Thu, 27 Jun 2019 08:58:07 +0000</pubDate>
      <link>https://dev.to/limal/i-forgot-to-buy-a-micro-hdmi-cable-for-my-raspberry-pi-4-2ocf</link>
      <guid>https://dev.to/limal/i-forgot-to-buy-a-micro-hdmi-cable-for-my-raspberry-pi-4-2ocf</guid>
      <description>&lt;h2&gt;
  
  
  Story
&lt;/h2&gt;

&lt;p&gt;I ordered my Raspberry Pi 4 one hour after its world premiere on Monday morning from a train to London. I rushed through the &lt;a href="https://thepihut.com/" rel="noopener noreferrer"&gt;https://thepihut.com/&lt;/a&gt; checkout process to ensure that I got my board before they run out of stock and, more importantly, while I still got a 3G signal on my mobile.&lt;/p&gt;

&lt;p&gt;I admit I got prompted by the website to buy an HDMI cable which I kindly refused to. After all I also have a Raspberry Pi Zero W which uses a Mini-HDMI port so I was sure I was fully covered and I didn't need yet another HDMI cable.&lt;/p&gt;

&lt;p&gt;How wrong I was! The next evening I received the board I noticed my Mini-HDMI is huge compared to the Micro-HDMI connectors that Raspberry Pi 4 use now.&lt;/p&gt;

&lt;p&gt;I quickly ordered a Micro-HDMI cable with a next-day delivery but I still wanted to use my newly arrived board now. No, I mean it. NOW!&lt;/p&gt;

&lt;h2&gt;
  
  
  Headless mode
&lt;/h2&gt;

&lt;p&gt;Luckily I knew there was a way to run the previous version of Raspberry Pi in a headless mode, i.e. without a display, keyboard and mouse. So I thought that maybe it would be possible to install the operating system without connecting it to a display too.&lt;/p&gt;

&lt;p&gt;And it was indeed. Here is how I did it. The key part was to skip the NOOBS installer entirely (even with its &lt;code&gt;silentinstall&lt;/code&gt; option which didn't work for me) and clone the Raspbian image directly onto the SD card.&lt;/p&gt;

&lt;p&gt;All you need to start enjoying your new board with only a power adapter at your disposal is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a Unix-based computer with an SD card reader&lt;/li&gt;
&lt;li&gt;an SD card&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Download Raspbian
&lt;/h2&gt;

&lt;p&gt;Go to &lt;a href="https://www.raspberrypi.org/downloads/raspbian/" rel="noopener noreferrer"&gt;https://www.raspberrypi.org/downloads/raspbian/&lt;/a&gt; and start downloading &lt;strong&gt;Raspbian Buster Lite&lt;/strong&gt; zip file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prepare your SD card
&lt;/h2&gt;

&lt;p&gt;Insert your SD card to your card reader and run &lt;code&gt;sudo fdisk -l&lt;/code&gt; to check its &lt;code&gt;/dev/sdX&lt;/code&gt; path.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F12bb12baqkr4tr26qvj3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F12bb12baqkr4tr26qvj3.jpg" width="600" height="140"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In my case the SD card reader was at &lt;code&gt;/dev/sde&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now run an app called &lt;code&gt;gparted&lt;/code&gt; and select your device from a drop-down in the top-right.&lt;/p&gt;

&lt;p&gt;If your SD card is a brand new one and has not been formatted yet you might need to create a partition table. Simply click &lt;em&gt;Device&lt;/em&gt; → &lt;em&gt;Create Partition Table…&lt;/em&gt; from the main menu.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Figpjkk75jzgyxj2nvcnx.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Figpjkk75jzgyxj2nvcnx.jpg" width="667" height="461"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can now create a new partition. Just create a single one with the maximum capacity and format it using &lt;code&gt;ext4&lt;/code&gt; filesystem.&lt;/p&gt;

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

&lt;p&gt;Extract an &lt;code&gt;2019-06-20-raspbian-buster-lite.img&lt;/code&gt; file from the downloaded &lt;em&gt;Raspbian Buster Lite&lt;/em&gt; zip package.&lt;/p&gt;

&lt;p&gt;Write the image onto the SD card using below command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo dd if=2019-06-20-raspbian-buster-lite.img of=/dev/sde bs=4M conv=fsync
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Enable SSH and setup WiFi
&lt;/h2&gt;

&lt;p&gt;The above &lt;code&gt;dd&lt;/code&gt; utility has created two partitions on your SD card: &lt;code&gt;boot&lt;/code&gt; and &lt;code&gt;roofs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Go to the &lt;code&gt;boot&lt;/code&gt; “directory in your file browser and create an empty file named &lt;code&gt;ssh&lt;/code&gt;. This will enable the SSH protocol for you Raspberry.&lt;/p&gt;

&lt;p&gt;Create another file in the same boot path named &lt;code&gt;wpa_supplicant.conf&lt;/code&gt; and paste below content to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;country=GB
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1

network={
    ssid="Your network name, e.g. BTHub1-123QWE"
    psk="Y0ur_P455w0rd"
    key_mgmt=WPA-PSK
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;country&lt;/code&gt;, &lt;code&gt;ssid&lt;/code&gt; and &lt;code&gt;psk&lt;/code&gt; values to match your WiFi settings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grande finale
&lt;/h2&gt;

&lt;p&gt;Your SD card is now ready. Insert it to your Raspberry Pi SD card slot at the bottom of the board.&lt;/p&gt;

&lt;p&gt;Connect a power adapter.&lt;/p&gt;

&lt;p&gt;Scan your local network (in my case &lt;code&gt;192.168.1.*&lt;/code&gt;) to see what IP address has been assigned to the Raspberry Pi.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo nmap -sS -p 22 192.168.1.0/24 | grep "Nmap scan"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can power off the board to see which of the IP addresses will disappear from the list to identify the Pi.&lt;/p&gt;

&lt;p&gt;Connect to the board:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh pi@192.168.1.101
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The password is &lt;code&gt;raspberry&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhwitmoqzm8vtmep3y3ip.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhwitmoqzm8vtmep3y3ip.jpg" width="800" height="299"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You're back in the game! Change your password with &lt;code&gt;passwd&lt;/code&gt; and enjoy!&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus! Change WiFi after installation
&lt;/h2&gt;

&lt;p&gt;Let's say you have changed your WiFi password but your Raspberry is no longer in a pre-installation mode. Don't worry! It's much easier to change WiFi password for a Raspberry that has an operating system already installed.&lt;/p&gt;

&lt;p&gt;Simply eject an SD card from your Raspberry and read it on your PC's card reader. Then locate the &lt;code&gt;/etc/wpa_supplicant/wpa_supplicant.conf&lt;/code&gt; file and replace your WiFi settings there. Remember to &lt;code&gt;sudo&lt;/code&gt; yourself in order to access and overwrite this file.&lt;/p&gt;

</description>
      <category>raspberrypi</category>
      <category>iot</category>
      <category>linux</category>
    </item>
  </channel>
</rss>
