<?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>Forem: Wojtek Siudzinski</title>
    <description>The latest articles on Forem by Wojtek Siudzinski (@suda).</description>
    <link>https://forem.com/suda</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%2F306550%2Fa753e01b-81dc-44c4-b265-f774d3dc09a2.jpeg</url>
      <title>Forem: Wojtek Siudzinski</title>
      <link>https://forem.com/suda</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/suda"/>
    <language>en</language>
    <item>
      <title>Quick and dirty auto-deployment to Raspberry Pi</title>
      <dc:creator>Wojtek Siudzinski</dc:creator>
      <pubDate>Sat, 17 Feb 2024 16:07:57 +0000</pubDate>
      <link>https://forem.com/suda/quick-and-dirty-auto-deployment-to-raspberry-pi-4lgk</link>
      <guid>https://forem.com/suda/quick-and-dirty-auto-deployment-to-raspberry-pi-4lgk</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XvoC0PVA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://suda.pl/content/images/2024/02/DALL-E-2024-02-17-17.07.02---Create-a-vector-style-illustration-depicting-the-deployment-of-code-to-a-Raspberry-Pi-via-Git.-The-image-should-feature-a-computer-screen-with-the-Git.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XvoC0PVA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://suda.pl/content/images/2024/02/DALL-E-2024-02-17-17.07.02---Create-a-vector-style-illustration-depicting-the-deployment-of-code-to-a-Raspberry-Pi-via-Git.-The-image-should-feature-a-computer-screen-with-the-Git.jpeg" alt="Quick and dirty auto-deployment to Raspberry Pi" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Some projects require a quick way to deploy code to a Raspberry Pi (or other small device running Linux behing NAT) and it's not critical enough to need things like Kubernetes, Ansible or Docker. It was exactly the case for a small RGB LED matrix we set up at our office to show some KPIs. It was running an OG Raspberry Pi A and we just want to be able to update the code remotly.&lt;/p&gt;

&lt;h3&gt;
  
  
  The setup
&lt;/h3&gt;

&lt;p&gt;To make the whole thing a bit easier we decided on a Git based setup. The Pi will keep checking a remote repo for changes and if there are any, it will pull them and restart the &lt;code&gt;systemd&lt;/code&gt; service driving the display.&lt;/p&gt;

&lt;h3&gt;
  
  
  Git setup
&lt;/h3&gt;

&lt;p&gt;The Pi needs to be able to access the repository therefore we needed to add the public SSH key of the Pi as a Deploy key:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generate a new key on the Pi (only if it doesn't have one yet) with:&lt;/li&gt;
&lt;/ol&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ssh-keygen -t ed25519
$ cat ~/.ssh/id_ed25519.pub
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Copy the resulting key and paste it in &lt;strong&gt;Settings&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Deploy keys&lt;/strong&gt; -&amp;gt; &lt;strong&gt;Add deploy key&lt;/strong&gt; in your GitHub repo&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Clone the repo on the Pi via ssh&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Main application service
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;First you need to define a &lt;code&gt;systemd&lt;/code&gt; service for your main application:&lt;/li&gt;
&lt;/ol&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Unit]
Description=rgb-matrix

[Service]
Type=simple
Restart=always
RestartSec=5
ExecStart=/home/pi/rgb-matrix/run.sh
WorkingDirectory=/home/pi/rgb-matrix
StandardOutput=append:/home/pi/rgb-matrix/stdout.log
StandardError=append:/home/pi/rgb-matrix/stderr.log
User=root
Group=root

[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In our case the application has to run as a &lt;code&gt;root&lt;/code&gt; but in your case there might be a better user for that. Once created, you need to enable it:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo ln -s ${PWD}/rgb-matrix.service /etc/systemd/system/rgb-matrix.service
$ sudo systemctl daemon-reload
$ sudo systemctl enable rgb-matrix.service
$ sudo systemctl start rgb-matrix.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Auto-deployment service
&lt;/h3&gt;

&lt;p&gt;Now we need a small script that will be periodically checking the remote repo:&lt;/p&gt;



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

# Configuration
SERVICE_NAME="rgb-matrix" # The name of the systemd service you want to restart

# Function to check for updates and pull them
check_and_pull() {
    git fetch origin

    # Check if there are updates available
    if ! git diff --quiet origin/main; then
        echo "Changes detected, pulling updates..."
        git pull
        echo "Updates pulled successfully."

        # Restart the systemd service
        echo "Restarting the service: $SERVICE_NAME..."
        systemctl restart "$SERVICE_NAME"
        echo "Service restarted successfully."
    fi
}

# Main loop to periodically check for updates
while true; do
    check_and_pull
    # Wait for a specified interval before checking again
    sleep 60 # Checks every 60 seconds, adjust as needed
done

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

&lt;/div&gt;



&lt;p&gt;Similarily to the main application, we need to create a &lt;code&gt;systemd&lt;/code&gt; service for the watcher:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Unit]
Description=watcher

[Service]
Type=simple
Restart=always
RestartSec=5
ExecStart=/home/pi/rgb-matrix/watcher.sh
WorkingDirectory=/home/pi/rgb-matrix
StandardOutput=append:/home/pi/rgb-matrix/watcher-stdout.log
StandardError=append:/home/pi/rgb-matrix/watcher-stderr.log
User=root
Group=root

[Install]
WantedBy=multi-user.target
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and enable it as well:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo ln -s ${PWD}/watcher.service /etc/systemd/system/watcher.service
$ sudo systemctl daemon-reload
$ sudo systemctl enable watcher.service
$ sudo systemctl start watcher.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Done
&lt;/h3&gt;

&lt;p&gt;Now any changes pushed to the remote repo will get automatically fetched and deployed. This of course does not take into account any migrations, installation of dependencies, rollbacks etc. but that's ok :)&lt;/p&gt;

</description>
      <category>git</category>
      <category>ssh</category>
      <category>deployment</category>
      <category>raspberrypi</category>
    </item>
    <item>
      <title>Spinning up a free IPFS webrtc-star discovery server with Heroku</title>
      <dc:creator>Wojtek Siudzinski</dc:creator>
      <pubDate>Mon, 25 May 2020 18:03:52 +0000</pubDate>
      <link>https://forem.com/suda/spinning-up-a-free-ipfs-webrtc-star-discovery-server-with-heroku-284p</link>
      <guid>https://forem.com/suda/spinning-up-a-free-ipfs-webrtc-star-discovery-server-with-heroku-284p</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SN11ig3X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://suda.pl/content/images/2020/05/Screenshot-from-2020-05-25-17-33-21.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SN11ig3X--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://suda.pl/content/images/2020/05/Screenshot-from-2020-05-25-17-33-21.png" alt="Spinning up a free IPFS webrtc-star discovery server with Heroku"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While building the &lt;strong&gt;&lt;a href="https://webmic.suda.pl/"&gt;Web Microphone&lt;/a&gt;&lt;/strong&gt; app with IPFS I sumbled upon an error when trying to use older examples but using the latest &lt;code&gt;js-ipfs&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Error: no valid addresses were provided for transports [WebSockets,WebRTCStar,Circuit]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It got me puzzled but Vasco cleared it out in this thread:&lt;/p&gt;


&lt;blockquote class="ltag__twitter-tweet"&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--jsh3lQxP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/1184444912793739264/_0l2k6vP_normal.jpg" alt="Vasco Santos profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        Vasco Santos
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @vascosantos10
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--52oNvK_0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/twitter-ff4bdab814039c4cb172a35ea369e0ea9c6a4b59b631a293896ae195fa26a99d.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      &lt;a href="https://twitter.com/dietrich"&gt;@dietrich&lt;/a&gt; &lt;a href="https://twitter.com/ThePatToner"&gt;&lt;/a&gt;&lt;a href="https://twitter.com/ThePatToner"&gt;@ThePatToner&lt;/a&gt; &lt;a href="https://twitter.com/jacobheun"&gt;@jacobheun&lt;/a&gt; &lt;a href="https://twitter.com/ThePatToner"&gt;&lt;/a&gt;&lt;a href="https://twitter.com/ThePatToner"&gt;@ThePatToner&lt;/a&gt; I see that you are trying to listen on a websocket-star address. Unfortunately, we did a poor job on announcing it, but we are no supporting websocket-star addresses since js-ipfs@0.41
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      15:38 PM - 19 May 2020
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1262769647482482689" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-reply-action.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1262769647482482689" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-retweet-action.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      0
      &lt;a href="https://twitter.com/intent/like?tweet_id=1262769647482482689" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-like-action.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
      0
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


&lt;p&gt;It turns out that it's necessary to use a new discovery mechanism, &lt;a href="https://github.com/libp2p/js-libp2p-webrtc-star"&gt;&lt;code&gt;webrtc-star&lt;/code&gt;&lt;/a&gt;. This seemed like an easy way to fix it, just change the server to the hosted randezvous server mentioned in readme and we're done! But nothing is ever that easy. It was failing for me with a 500 error and I shouldn't be surprised as the documentation clearly states:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;it &lt;strong&gt;should not be used for apps in production&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The recommended route is to deploy it on your own. So I tried to get it done in shortes amount of time and preferably without a need to pay for the whole server. Fortunately, Heroku supports Docker containers now and they can be deployed literally with couple of lines! Here's how you do it:&lt;/p&gt;

&lt;h3&gt;
  
  
  Instructions
&lt;/h3&gt;

&lt;p&gt;First install the &lt;a href="https://devcenter.heroku.com/articles/heroku-cli"&gt;Heroku CLI&lt;/a&gt; and then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Login to Heroku&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;heroku login
&lt;span class="c"&gt;# Login to the Container Registry&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;heroku container:login
&lt;span class="c"&gt;# Clone the webrtc-star repo&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git clone https://github.com/libp2p/js-libp2p-webrtc-star.git
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;js-libp2p-webrtc-star
&lt;span class="c"&gt;# Create a Heroku app&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;heroku create
&lt;span class="c"&gt;# Build and push the image&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;heroku container:push web
&lt;span class="c"&gt;# Release the image to your app&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;heroku container:release web
&lt;span class="c"&gt;# Scale to one free worker&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;heroku ps:scale &lt;span class="nv"&gt;web&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;span class="c"&gt;# Open the app in the browser&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;heroku open

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



&lt;p&gt;Now the server should welcome you with its address:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tnZX3pWN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://suda.pl/content/images/2020/05/Screenshot-from-2020-05-25-17-25-25.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tnZX3pWN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://suda.pl/content/images/2020/05/Screenshot-from-2020-05-25-17-25-25.png" alt="Spinning up a free IPFS webrtc-star discovery server with Heroku"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Use this address in your IPFS app and you're done!&lt;/p&gt;

</description>
      <category>ipfs</category>
      <category>webrtc</category>
      <category>jsipfs</category>
      <category>heroku</category>
    </item>
    <item>
      <title>Go for Particle Argon, Boron or Xenon</title>
      <dc:creator>Wojtek Siudzinski</dc:creator>
      <pubDate>Thu, 16 Apr 2020 13:28:57 +0000</pubDate>
      <link>https://forem.com/suda/go-for-particle-argon-boron-or-xenon-23oe</link>
      <guid>https://forem.com/suda/go-for-particle-argon-boron-or-xenon-23oe</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--PGKf5zdu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://suda.pl/content/images/2020/04/Argon_tinygo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--PGKf5zdu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://suda.pl/content/images/2020/04/Argon_tinygo.png" alt="Go for Particle Argon, Boron or Xenon"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the release of &lt;strong&gt;TinyGo&lt;/strong&gt; 0.13, you can now &lt;strong&gt;write firmware for Particle Argon, Boron and Xenon in Go&lt;/strong&gt;:&lt;/p&gt;


&lt;blockquote class="ltag__twitter-tweet"&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--Q7koEj6U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/1087628455234154496/4L3cmtCY_normal.jpg" alt="TinyGo profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        TinyGo
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @tinygolang
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mL-RlVCA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/twitter-95c212266bf9a35c02dd777b6d438dfbc5a6a4c1e82708c3ab617b069928387a.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      We just released version 0.13! Go 1.14 + LLVM 10 + support for 2^5 boards like the &lt;a href="https://twitter.com/particle"&gt;@particle&lt;/a&gt; &lt;a href="https://twitter.com/hashtag/argon"&gt;#argon&lt;/a&gt; + &lt;a href="https://twitter.com/hashtag/WebAssembly"&gt;#WebAssembly&lt;/a&gt; updates + so much more. Massive thank you to our amazing contributors!&lt;br&gt;&lt;br&gt;&lt;a href="https://t.co/OJC4FBnsKk"&gt;github.com/tinygo-org/tin…&lt;/a&gt;&lt;br&gt;&lt;br&gt;&lt;a href="https://twitter.com/hashtag/golang"&gt;#golang&lt;/a&gt; &lt;a href="https://twitter.com/hashtag/llvm"&gt;#llvm&lt;/a&gt; &lt;a href="https://twitter.com/hashtag/tinygo"&gt;#tinygo&lt;/a&gt; &lt;a href="https://twitter.com/hashtag/wasm"&gt;#wasm&lt;/a&gt; &lt;a href="https://twitter.com/hashtag/microcontrollers"&gt;#microcontrollers&lt;/a&gt; &lt;a href="https://twitter.com/hashtag/embedded"&gt;#embedded&lt;/a&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      18:14 PM - 14 Apr 2020
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1250125364321533952" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-reply-action.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1250125364321533952" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-retweet-action.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      32
      &lt;a href="https://twitter.com/intent/like?tweet_id=1250125364321533952" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-like-action.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
      70
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


&lt;p&gt;There are some caveats for now, namely:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;TinyGo can't coexist with the Particle Device OS. Meaning flashing your Go app to the device will erase Particle bootloader and system. It is still possible to restore the device to "stock" but it requires manual flashing of all necessary parts.&lt;/li&gt;
&lt;li&gt;TinyGo doesn't support Argon's WiFi coprocessor nor Boron's cellular modems yet, meaning for now you can only use the devices offline. Ayke is making progress on support for the &lt;a href="https://github.com/aykevl/go-bluetooth/pull/2#issuecomment-595768670"&gt;nRF Soft Device&lt;/a&gt; that allows BLE support (and Mesh in the future) but it's still experimental.&lt;/li&gt;
&lt;li&gt;You need to use the &lt;a href="https://store.particle.io/products/particle-debugger"&gt;Particle Debugger&lt;/a&gt; to do all the flashing. It might be possible to use &lt;a href="https://github.com/adafruit/Adafruit_nRF52_Bootloader"&gt;Adafruit nRF52 Bootloader&lt;/a&gt; (same as used for CircuitPython) in the future to allow flashing over the USB.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That being said, running TinyGo might be a nice alternative to &lt;a href="https://docs.particle.io/tutorials/learn-more/xenon-circuit-python/"&gt;CircuitPython&lt;/a&gt; if you need more speed. How fast is it? Well, it can handle a real-time flight controller for a drone on Arduino Nano33 IoT with SAMD21 Cortex-M0 at 48 MHz:&lt;/p&gt;

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

&lt;p&gt;Or drive six 32x32 RGB LED matrices on Cortex-M4:&lt;/p&gt;


&lt;blockquote class="ltag__twitter-tweet"&gt;
      &lt;div class="ltag__twitter-tweet__media ltag__twitter-tweet__media__video-wrapper"&gt;
        &lt;div class="ltag__twitter-tweet__media--video-preview"&gt;
          &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nNfrg92z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/ext_tw_video_thumb/1238889282175217666/pu/img/9Qi8sdqZ1r-hD4Cl.jpg" alt="unknown tweet media content"&gt;
          &lt;img src="/assets/play-butt.svg" class="ltag__twitter-tweet__play-butt" alt="Play butt"&gt;
        &lt;/div&gt;
        &lt;div class="ltag__twitter-tweet__video"&gt;
          
            
          
        &lt;/div&gt;
      &lt;/div&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--M52VoDS3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/622680915567718400/67jAnIAs_normal.jpg" alt="conejo 🐇🐰⚡ profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        conejo 🐇🐰⚡
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @_conejo
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--mL-RlVCA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://practicaldev-herokuapp-com.freetls.fastly.net/assets/twitter-95c212266bf9a35c02dd777b6d438dfbc5a6a4c1e82708c3ab617b069928387a.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      Do you want to make your own? The code is open source! &amp;gt;&amp;gt; &lt;a href="https://t.co/b8wnO6Qu30"&gt;github.com/aykevl/things/…&lt;/a&gt; &lt;a href="https://twitter.com/TinyGolang"&gt;@TinyGolang&lt;/a&gt; 
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      18:08 PM - 14 Mar 2020
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1238889662804045826" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-reply-action.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1238889662804045826" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-retweet-action.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      4
      &lt;a href="https://twitter.com/intent/like?tweet_id=1238889662804045826" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="/assets/twitter-like-action.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
      20
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


&lt;p&gt;Additionally, a strong advantage of TinyGo is that &lt;strong&gt;TinyGo is written in Go&lt;/strong&gt; itself! Meaning, you don't need to know C/C++ to understand/contribute or write low-level code.&lt;/p&gt;

&lt;p&gt;Got you interested? Let's proceed to the...&lt;/p&gt;

&lt;h1&gt;
  
  
  Instructions
&lt;/h1&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://store.particle.io/collections/bluetooth"&gt;Particle Argon/Boron/Xenon&lt;/a&gt; $11-$55&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://store.particle.io/products/particle-debugger"&gt;Particle Debugger&lt;/a&gt; $12&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Necessary software
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Install OpenOCD. If you have &lt;a href="https://www.particle.io/workbench/"&gt;Particle Workbench&lt;/a&gt; installed, it should already download it for you to &lt;code&gt;~/.particle/toolchains/openocd&lt;/code&gt; directory. Make sure the &lt;code&gt;openocd&lt;/code&gt; binary is in your &lt;code&gt;PATH&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tinygo.org/getting-started/"&gt;Install TinyGo&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Blinky
&lt;/h3&gt;

&lt;p&gt;Well, that's it really. Now you're ready to go write Go:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c"&gt;// machine package exposes all hardware available on the microcontroller&lt;/span&gt;
    &lt;span class="s"&gt;"machine"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// All the pins are listed here: https://tinygo.org/microcontrollers/machine/particle-xenon/&lt;/span&gt;
    &lt;span class="n"&gt;led&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LED&lt;/span&gt;
    &lt;span class="c"&gt;// Make it an output&lt;/span&gt;
    &lt;span class="n"&gt;led&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PinConfig&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Mode&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;machine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PinOutput&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="c"&gt;// Repeat forever&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// Turn it on (it's active low)&lt;/span&gt;
        &lt;span class="n"&gt;led&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Low&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Millisecond&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c"&gt;// And off&lt;/span&gt;
        &lt;span class="n"&gt;led&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;High&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Millisecond&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;500&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 can flash your app with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;tinygo flash &lt;span class="nt"&gt;-target&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;particle-xenon blink

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



&lt;p&gt;(assuming you save the Go code above in &lt;code&gt;blink&lt;/code&gt; directory). Also, you should set the &lt;code&gt;-target&lt;/code&gt; flag to the module you're using.&lt;/p&gt;

&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;Even although Argon and Boron can't connect to the internet yet, peripherals like UART, GPIO, SPI, I2C, ADC, and PWM are working, and there are already &lt;a href="https://github.com/tinygo-org/drivers"&gt;plenty of drivers&lt;/a&gt; for devices like displays, sensors, etc.&lt;/p&gt;

&lt;p&gt;I hope you'll have fun with TinyGo and if you like to chat about it or contribute, feel free to join the &lt;a href="https://gophers.slack.com/messages/CDJD3SUP6/"&gt;Slack channel&lt;/a&gt; and check out the &lt;a href="https://github.com/tinygo-org"&gt;GitHub repos&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>particle</category>
      <category>tinygo</category>
      <category>go</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Single Dockerfile for testing and production using multi-stage builds</title>
      <dc:creator>Wojtek Siudzinski</dc:creator>
      <pubDate>Fri, 27 Mar 2020 11:11:07 +0000</pubDate>
      <link>https://forem.com/suda/single-dockerfile-for-testing-and-production-using-multi-stage-builds-189o</link>
      <guid>https://forem.com/suda/single-dockerfile-for-testing-and-production-using-multi-stage-builds-189o</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2wVslahT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.unsplash.com/photo-1531206715517-5c0ba140b2b8%3Fixlib%3Drb-1.2.1%26q%3D80%26fm%3Djpg%26crop%3Dentropy%26cs%3Dtinysrgb%26w%3D1080%26fit%3Dmax%26ixid%3DeyJhcHBfaWQiOjExNzczfQ" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2wVslahT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.unsplash.com/photo-1531206715517-5c0ba140b2b8%3Fixlib%3Drb-1.2.1%26q%3D80%26fm%3Djpg%26crop%3Dentropy%26cs%3Dtinysrgb%26w%3D1080%26fit%3Dmax%26ixid%3DeyJhcHBfaWQiOjExNzczfQ" alt="Single Dockerfile for testing and production using multi-stage builds"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In different contexts like development, running tests or serving production, our apps have different needs when it comes to their Docker images. For development, you might want a live-reload server with all necessary dependencies. For testing you probably need some more packages that you &lt;strong&gt;definitely&lt;/strong&gt; don't want on production.&lt;/p&gt;

&lt;p&gt;There are ways to go about it, the first one that pops in mind is using multiple Dockerfiles and just telling Docker which one to use. But this approach has its limits, especially along the road, where each file can very easily develop its own life, diverging from each other beyond recognition.&lt;/p&gt;

&lt;p&gt;Fortunately since Docker 17.05 we have the concept of &lt;a href="https://docs.docker.com/develop/develop-images/multistage-build/#use-a-previous-stage-as-a-new-stage"&gt;multi-stage builds&lt;/a&gt;. An ability to declare separate images in one &lt;code&gt;Dockerfile&lt;/code&gt; and freely inherit or copy data between each other. This is the exact approach I used when building images for &lt;a href="https://lox.sh/?source=dev.to"&gt;Lox&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;#############################################&lt;/span&gt;
&lt;span class="c"&gt;# Base container with all necessary deps&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; tiangolo/uvicorn-gunicorn:python3.7-alpine3.8 AS base&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; HOME=/app &lt;/span&gt;
    BUILD_DEPS="build-base linux-headers postgresql postgresql-dev libffi-dev"
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; ${HOME}&lt;/span&gt;

&lt;span class="c"&gt;# Copy the pipenv files&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; Pipfile ${WORKDIR}/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; Pipfile.lock ${WORKDIR}/&lt;/span&gt;

&lt;span class="c"&gt;# 1. Install system dependencies&lt;/span&gt;
&lt;span class="c"&gt;# 2. Install pipenv&lt;/span&gt;
&lt;span class="c"&gt;# 3. Use pipenv to install app deps&lt;/span&gt;
&lt;span class="c"&gt;# 4. Remove system deps to save space&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BUILD_DEPS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; pipenv &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pipenv &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apk del &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BUILD_DEPS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;#############################################&lt;/span&gt;
&lt;span class="c"&gt;# Test container from a common base&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; base AS test&lt;/span&gt;
&lt;span class="c"&gt;# Same as in the base image but this time we also install the --dev packages&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BUILD_DEPS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; pipenv &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pipenv &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--dev&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apk del &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BUILD_DEPS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;#############################################&lt;/span&gt;
&lt;span class="c"&gt;# Live container with Webpack watcher&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; base AS live&lt;/span&gt;
&lt;span class="c"&gt;# Install Node.js dependencies&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;apk add &lt;span class="nt"&gt;--no-cache&lt;/span&gt; nodejs npm
&lt;span class="c"&gt;# Install all Webpack dependencies&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json package-lock.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="c"&gt;# Finally copy the current sources and build the bundle&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run bundle:build

&lt;span class="c"&gt;#############################################&lt;/span&gt;
&lt;span class="c"&gt;# Final container with the app&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; base AS production&lt;/span&gt;
&lt;span class="c"&gt;# Only copy the ready app dir from the live step&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=live ${WORKDIR} ${WORKDIR}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; for the sake of brevity, this &lt;code&gt;Dockerfile&lt;/code&gt; doesn't contain all size improvements and will produce slightly bigger images.&lt;/p&gt;

&lt;p&gt;Now you're able to build separate images for each of your needs:&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; suda/lox/test &lt;span class="nt"&gt;--target&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Development w/ live-reload
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; suda/lox/live &lt;span class="nt"&gt;--target&lt;/span&gt; live &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Production
&lt;/h3&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; suda/lox &lt;span class="nt"&gt;--target&lt;/span&gt; production &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;P.S.:&lt;/strong&gt; You might've noticed I used the great Python base image from &lt;a href="https://github.com/tiangolo"&gt;Sebastián Ramírez&lt;/a&gt; and if you want to deploy a Django project using ASGI, &lt;a href="https://suda.pl/deploying-django-3-asgi/"&gt;I have some instructions that can help you&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>multistage</category>
      <category>dockerfile</category>
      <category>python</category>
    </item>
    <item>
      <title>Production-ready Django 3 ASGI Docker image</title>
      <dc:creator>Wojtek Siudzinski</dc:creator>
      <pubDate>Thu, 02 Jan 2020 16:49:49 +0000</pubDate>
      <link>https://forem.com/suda/production-ready-django-3-asgi-docker-image-1d49</link>
      <guid>https://forem.com/suda/production-ready-django-3-asgi-docker-image-1d49</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3fBOOkXD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://suda.pl/content/images/2020/01/spacex-OHOU-5UVIYQ.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3fBOOkXD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://suda.pl/content/images/2020/01/spacex-OHOU-5UVIYQ.jpg" alt="Production-ready Django 3 ASGI Docker image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With &lt;a href="https://docs.djangoproject.com/en/3.0/releases/3.0/#asgi-support"&gt;Django 3 released&lt;/a&gt;, it's a great moment to jump in on all the async goodies it provides. Unfortunately for me, it means dropping my &lt;a href="https://suda.pl/deploying-django-with-uwsgi/"&gt;uWSGI Docker config&lt;/a&gt; and figuring out a new approach.&lt;/p&gt;

&lt;p&gt;Fortunately, Sebastián Ramírez of &lt;code&gt;fastapi&lt;/code&gt; fame, created a very nice base image using &lt;code&gt;uvicorn&lt;/code&gt;: &lt;a href="https://github.com/tiangolo/uvicorn-gunicorn-docker"&gt;uvicorn-gunicorn-docker&lt;/a&gt;. One thing missing was the ability to serve static files, but here's where &lt;a href="http://whitenoise.evans.io/en/stable/base.html"&gt;&lt;code&gt;WhiteNoise&lt;/code&gt;&lt;/a&gt; comes in.&lt;/p&gt;

&lt;p&gt;Let's jump in to the setup.&lt;/p&gt;

&lt;h1&gt;
  
  
  Instructions
&lt;/h1&gt;

&lt;p&gt;Compared to my &lt;a href="https://suda.pl/deploying-django-with-uwsgi/"&gt;uWSGI setup&lt;/a&gt;, here we just need to create a &lt;code&gt;Dockerfile&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; PYTHON_VERSION=3.7&lt;/span&gt;

&lt;span class="c"&gt;# Build dependencies in separate container&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; tiangolo/uvicorn-gunicorn:python${PYTHON_VERSION}-alpine3.8 AS builder&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; WORKDIR /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; Pipfile ${WORKDIR}/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; Pipfile.lock ${WORKDIR}/&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;WORKDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pip &lt;span class="nb"&gt;install &lt;/span&gt;pipenv &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pipenv &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--system&lt;/span&gt;

&lt;span class="c"&gt;# Create the final container with the app&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; tiangolo/uvicorn-gunicorn:python${PYTHON_VERSION}-alpine3.8&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; USER=docker \&lt;/span&gt;
    GROUP=docker \
    UID=12345 \
    GID=23456 \
    HOME=/app \
    PYTHONUNBUFFERED=1
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; ${HOME}&lt;/span&gt;

&lt;span class="c"&gt;# Create user/group&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;--gid&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GROUP&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; adduser &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--disabled-password&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--gecos&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--home&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--ingroup&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GROUP&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--no-create-home&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--uid&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;UID&lt;/span&gt;&lt;span class="k"&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="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Run as docker user&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; ${USER}&lt;/span&gt;
&lt;span class="c"&gt;# Copy installed packages&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/local/lib/python3.7/site-packages /usr/local/lib/python3.7/site-packages&lt;/span&gt;
&lt;span class="c"&gt;# Copy the application&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=docker:docker . .&lt;/span&gt;
&lt;span class="c"&gt;# Collect the static files&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;python manage.py collectstatic &lt;span class="nt"&gt;--noinput&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Serving the static files
&lt;/h2&gt;

&lt;p&gt;For this purpouse, I'm using &lt;a href="http://whitenoise.evans.io/en/stable/base.html"&gt;&lt;code&gt;WhiteNoise&lt;/code&gt;&lt;/a&gt; which needs to be added to dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;pipenv &lt;span class="nb"&gt;install &lt;/span&gt;whitenoise
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;and then added &lt;code&gt;whitenoise.middleware.WhiteNoiseMiddleware&lt;/code&gt; to the top of the &lt;code&gt;MIDDLEWARE&lt;/code&gt; array in &lt;code&gt;settings.py&lt;/code&gt;, right below the &lt;code&gt;SecurityMiddleware&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;MIDDLEWARE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s"&gt;'django.middleware.security.SecurityMiddleware'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;'whitenoise.middleware.WhiteNoiseMiddleware'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&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;strong&gt;Side note:&lt;/strong&gt; I know many people are wondering why I'm so set on serving static files, instead of just using S3 buit I think &lt;a href="http://whitenoise.evans.io/en/stable/index.html#shouldn-t-i-be-pushing-my-static-files-to-s3-using-something-like-django-storages"&gt;David explained it well&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Shouldn’t I be pushing my static files to S3 using something like Django-Storages?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
No. (...) problem with a push-based approach to handling static files is that it adds complexity and fragility to your deployment process.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Running the image
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;uvicorn&lt;/code&gt; needs to know what is the module is the main application, therefore we need to set the &lt;code&gt;APP_MODULE&lt;/code&gt; environment variable to &lt;code&gt;myapp.asgi:application&lt;/code&gt; replacing &lt;code&gt;myapp&lt;/code&gt; with the name of your app. This assumes you have the &lt;code&gt;asgi.py&lt;/code&gt; file (ASGI equivalent of the &lt;code&gt;wsgi.py&lt;/code&gt;) which will be generated automatically when creating new Django 3.0 project but if you're migrating from an older version, you can use this one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="s"&gt;"""
ASGI config for myapp project.

It exposes the ASGI callable as a module-level variable named ``application``.

For more information on this file, see
https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/
"""&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.asgi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;get_asgi_application&lt;/span&gt;

&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setdefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'DJANGO_SETTINGS_MODULE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'myapp.settings'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;application&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_asgi_application&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  Notes
&lt;/h1&gt;

&lt;p&gt;Running in &lt;a href="https://docs.djangoproject.com/en/3.0/topics/async/"&gt;async mode&lt;/a&gt;, means you should not use possibly blocking, synchronous methods (like the ORM) in the main thread. &lt;a href="https://forum.djangoproject.com/t/is-there-a-way-to-disable-the-synchronousonlyoperation-check-when-using-the-orm-in-a-jupyter-notebook/548/3"&gt;Quick tip&lt;/a&gt; here is, that if you get &lt;code&gt;SynchronousOnlyOperation&lt;/code&gt; exception you might want to wrap it with &lt;code&gt;sync_to_async&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;asgiref.sync&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sync_to_async&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;my_db_function&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;do&lt;/span&gt; &lt;span class="n"&gt;orm&lt;/span&gt; &lt;span class="n"&gt;stuff&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;sync_to_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;my_db_function&lt;/span&gt;&lt;span class="p"&gt;)()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



</description>
      <category>django</category>
      <category>asgi</category>
      <category>docker</category>
      <category>async</category>
    </item>
    <item>
      <title>Deploying Django to production with uWSGI</title>
      <dc:creator>Wojtek Siudzinski</dc:creator>
      <pubDate>Sun, 01 Sep 2019 12:28:00 +0000</pubDate>
      <link>https://forem.com/suda/deploying-django-to-production-with-uwsgi-54pa</link>
      <guid>https://forem.com/suda/deploying-django-to-production-with-uwsgi-54pa</guid>
      <description>&lt;p&gt;There are many posts about dockerizing Django apps but I feel like there's some room for improvement.&lt;/p&gt;

&lt;p&gt;Many of the approaches only focused on development flow, making the resulting image great for iterating but not ideal for production usage. Other ones, produced very big images, were &lt;a href="https://medium.com/goglides/stop-running-an-application-inside-a-docker-container-as-the-root-user-f255a7810c0c"&gt;running the application as &lt;code&gt;root&lt;/code&gt;&lt;/a&gt; or ignored handling static files and deferred it to S3. With these images I had several goals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Production-ready image (following general security tips)&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;alpine&lt;/code&gt; base for a small image&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.docker.com/develop/develop-images/multistage-build/"&gt;Multi-stage&lt;/a&gt; build for even smaller and cleaner image&lt;/li&gt;
&lt;li&gt;Handling static files inside the same container&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I decided on using &lt;a href="https://uwsgi-docs.readthedocs.io/en/latest/"&gt;&lt;code&gt;uWSGI&lt;/code&gt;&lt;/a&gt; which actually also can serve static files, making my life much easier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; I'm using &lt;a href="https://pipenv.readthedocs.io/en/latest/"&gt;pipenv&lt;/a&gt; to manage virtualenv and all dependencies (and I recommend you use it too), therefore some instructions might need tweaking if you're using bare &lt;code&gt;pip&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note 2:&lt;/strong&gt; If you prefer running in ASGI, I wrote instructions &lt;a href="https://dev.to/suda/production-ready-django-3-asgi-docker-image-1d49"&gt;for &lt;code&gt;uvicorn&lt;/code&gt; based Docker image&lt;/a&gt; as well.&lt;/p&gt;

&lt;h1&gt;
  
  
  Instructions
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Setup &lt;code&gt;uWSGI&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Start by adding it to your dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;pipenv &lt;span class="nb"&gt;install &lt;/span&gt;uwsgi
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Then create &lt;code&gt;uwsgi.ini&lt;/code&gt; file in your project root directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[uwsgi]&lt;/span&gt;
&lt;span class="py"&gt;chdir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/app&lt;/span&gt;
&lt;span class="py"&gt;uid&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$(UID)&lt;/span&gt;
&lt;span class="py"&gt;gid&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$(GID)&lt;/span&gt;
&lt;span class="py"&gt;module&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$(UWSGI_MODULE)&lt;/span&gt;
&lt;span class="py"&gt;processes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$(UWSGI_PROCESSES)&lt;/span&gt;
&lt;span class="py"&gt;threads&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$(UWSGI_THREADS)&lt;/span&gt;
&lt;span class="py"&gt;procname-prefix-spaced&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;uwsgi:$(UWSGI_MODULE)&lt;/span&gt;

&lt;span class="py"&gt;http-socket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;:8080&lt;/span&gt;
&lt;span class="py"&gt;http-enable-proxy-protocol&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1&lt;/span&gt;
&lt;span class="py"&gt;http-auto-chunked&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;http-keepalive&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;75&lt;/span&gt;
&lt;span class="py"&gt;http-timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;75&lt;/span&gt;
&lt;span class="py"&gt;stats&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;:1717&lt;/span&gt;
&lt;span class="py"&gt;stats-http&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1&lt;/span&gt;
&lt;span class="py"&gt;offload-threads&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;$(UWSGI_OFFLOAD_THREADS)&lt;/span&gt;

&lt;span class="c"&gt;# Better startup/shutdown in docker:
&lt;/span&gt;&lt;span class="py"&gt;die-on-term&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1&lt;/span&gt;
&lt;span class="py"&gt;lazy-apps&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;0&lt;/span&gt;

&lt;span class="py"&gt;vacuum&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1&lt;/span&gt;
&lt;span class="py"&gt;master&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1&lt;/span&gt;
&lt;span class="py"&gt;enable-threads&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;thunder-lock&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1&lt;/span&gt;
&lt;span class="py"&gt;buffer-size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;65535&lt;/span&gt;

&lt;span class="c"&gt;# Logging
&lt;/span&gt;&lt;span class="py"&gt;log-x-forwarded-for&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;true&lt;/span&gt;

&lt;span class="c"&gt;# Avoid errors on aborted client connections
&lt;/span&gt;&lt;span class="py"&gt;ignore-sigpipe&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;ignore-write-errors&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;disable-write-exception&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;true&lt;/span&gt;

&lt;span class="py"&gt;no-defer-accept&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;1&lt;/span&gt;

&lt;span class="c"&gt;# Limits, Kill requests after 120 seconds
&lt;/span&gt;&lt;span class="py"&gt;harakiri&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;120&lt;/span&gt;
&lt;span class="py"&gt;harakiri-verbose&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;post-buffering&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;4096&lt;/span&gt;

&lt;span class="c"&gt;# Custom headers
&lt;/span&gt;&lt;span class="py"&gt;add-header&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;X-Content-Type-Options: nosniff&lt;/span&gt;
&lt;span class="py"&gt;add-header&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;X-XSS-Protection: 1; mode=block&lt;/span&gt;
&lt;span class="py"&gt;add-header&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;Strict-Transport-Security: max-age=16070400&lt;/span&gt;
&lt;span class="py"&gt;add-header&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;Connection: Keep-Alive&lt;/span&gt;

&lt;span class="c"&gt;# Static file serving with caching headers and gzip
&lt;/span&gt;&lt;span class="py"&gt;static-map&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/static=/app/staticfiles&lt;/span&gt;
&lt;span class="py"&gt;static-map&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/media=/app/media&lt;/span&gt;
&lt;span class="py"&gt;static-safe&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/usr/local/lib/python3.7/site-packages/&lt;/span&gt;
&lt;span class="py"&gt;static-gzip-dir&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/app/staticfiles/&lt;/span&gt;
&lt;span class="py"&gt;static-expires&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/app/staticfiles/CACHE/* $(UWSGI_STATIC_EXPIRES)&lt;/span&gt;
&lt;span class="py"&gt;static-expires&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/app/media/cache/* $(UWSGI_STATIC_EXPIRES)&lt;/span&gt;
&lt;span class="py"&gt;static-expires&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/app/staticfiles/frontend/img/* $(UWSGI_STATIC_EXPIRES)&lt;/span&gt;
&lt;span class="py"&gt;static-expires&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/app/staticfiles/frontend/fonts/* $(UWSGI_STATIC_EXPIRES)&lt;/span&gt;
&lt;span class="py"&gt;static-expires&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;/app/* 3600&lt;/span&gt;
&lt;span class="py"&gt;route-uri&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;^/static/ addheader:Vary: Accept-Encoding&lt;/span&gt;
&lt;span class="py"&gt;error-route-uri&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;^/static/ addheader:Cache-Control: no-cache&lt;/span&gt;

&lt;span class="c"&gt;# Cache stat() calls
&lt;/span&gt;&lt;span class="py"&gt;cache2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;name=statcalls,items=30&lt;/span&gt;
&lt;span class="py"&gt;static-cache-paths&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;86400&lt;/span&gt;

&lt;span class="c"&gt;# Redirect http -&amp;gt; https
&lt;/span&gt;&lt;span class="py"&gt;route-if&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;equal:${HTTP_X_FORWARDED_PROTO};http redirect-permanent:https://${HTTP_HOST}${REQUEST_URI}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The author of the config above is &lt;a href="https://github.com/nginxinc/kubernetes-ingress/issues/143#issuecomment-347814243"&gt;Diederik van der Boor&lt;/a&gt; 🙇&lt;/p&gt;

&lt;h2&gt;
  
  
  Create the &lt;code&gt;Dockerfile&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight"&gt;&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build argument allowing to change Python version&lt;/span&gt;
&lt;span class="k"&gt;ARG&lt;/span&gt;&lt;span class="s"&gt; PYTHON_VERSION=3.7&lt;/span&gt;

&lt;span class="c"&gt;# Build dependencies in a separate container&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:${PYTHON_VERSION}-alpine AS builder&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; WORKDIR /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; Pipfile ${WORKDIR}/&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; Pipfile.lock ${WORKDIR}/&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;WORKDIR&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pip &lt;span class="nb"&gt;install &lt;/span&gt;pipenv &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; pipenv &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--system&lt;/span&gt;

&lt;span class="c"&gt;# Create the final container with the app&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:${PYTHON_VERSION}-alpine&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; USER=docker \&lt;/span&gt;
    GROUP=docker \
    UID=12345 \
    GID=23456 \
    HOME=/app \
    PYTHONUNBUFFERED=1
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; ${HOME}&lt;/span&gt;

&lt;span class="c"&gt;# Create user/group&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;--gid&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GROUP&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; adduser &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--disabled-password&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--gecos&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--home&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--ingroup&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GROUP&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--no-create-home&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="nt"&gt;--uid&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;UID&lt;/span&gt;&lt;span class="k"&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="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;USER&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Run as docker user&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; ${USER}&lt;/span&gt;
&lt;span class="c"&gt;# Copy installed packages&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/local/lib/python3.7/site-packages /usr/local/lib/python3.7/site-packages&lt;/span&gt;
&lt;span class="c"&gt;# Copy uWSGI binary&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/local/bin/uwsgi /usr/local/bin/uwsgi&lt;/span&gt;
&lt;span class="c"&gt;# Copy the application&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=docker:docker . .&lt;/span&gt;
&lt;span class="c"&gt;# Collect static files&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;python manage.py collectstatic &lt;span class="nt"&gt;--noinput&lt;/span&gt;

&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["uwsgi", "--ini", "uwsgi.ini"]&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Start container
&lt;/h2&gt;

&lt;p&gt;To work correctly, you need to set some environment variables. If you're using Docker Compose, you could use similar &lt;code&gt;docker-compose.yml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3"&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;foo/bar&lt;/span&gt;
    &lt;span class="na"&gt;ports&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;8080:8080"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;UWSGI_MODULE=myapp.wsgi:application&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;UWSGI_PROCESSES=10&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;UWSGI_THREADS=2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;UWSGI_OFFLOAD_THREADS=10&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;UWSGI_STATIC_EXPIRES=86400&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;then you can build the image with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker-compose build app
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;I'm quite happy with this setup and it has been working in production for some time. Please let me know if you have any comments and if I could improve this more!&lt;/p&gt;

</description>
      <category>django</category>
      <category>uwsgi</category>
      <category>docker</category>
      <category>python</category>
    </item>
  </channel>
</rss>
