<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Wojtek @suda Siudzinski]]></title><description><![CDATA[Python/Node/Golang/Rust developer, DIY hacker, rookie designer, 3D print junkie. CEO @ Gaia Charge]]></description><link>https://suda.pl/</link><image><url>https://suda.pl/favicon.png</url><title>Wojtek @suda Siudzinski</title><link>https://suda.pl/</link></image><generator>Ghost 2.20</generator><lastBuildDate>Tue, 13 Jan 2026 17:30:34 GMT</lastBuildDate><atom:link href="https://suda.pl/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Quick and dirty auto-deployment to Raspberry Pi]]></title><description><![CDATA[<p>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</p>]]></description><link>https://suda.pl/quick-and-dirty-autodeployment-to-raspberry-pi/</link><guid isPermaLink="false">65d0cc0a92bff700010df7e0</guid><category><![CDATA[Git]]></category><category><![CDATA[SSH]]></category><category><![CDATA[deployment]]></category><category><![CDATA[Raspberry Pi]]></category><category><![CDATA[systemd]]></category><dc:creator><![CDATA[Wojtek Siudzinski]]></dc:creator><pubDate>Sat, 17 Feb 2024 16:07:57 GMT</pubDate><media:content url="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" medium="image"/><content:encoded><![CDATA[<img src="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"><p>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.</p><h3 id="the-setup">The setup</h3><p>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 <code>systemd</code> service driving the display.</p><h3 id="git-setup">Git setup</h3><p>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:</p><p>1. Generate a new key on the Pi (only if it doesn't have one yet) with:</p><!--kg-card-begin: code--><pre><code>$ ssh-keygen -t ed25519
$ cat ~/.ssh/id_ed25519.pub</code></pre><!--kg-card-end: code--><p>2. Copy the resulting key and paste it in <strong>Settings</strong> -&gt; <strong>Deploy keys</strong> -&gt; <strong>Add deploy key</strong> in your GitHub repo</p><p>3. Clone the repo on the Pi via ssh</p><h3 id="main-application-service">Main application service</h3><p>1. First you need to define a <code>systemd</code> service for your main application:</p><!--kg-card-begin: code--><pre><code>[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</code></pre><!--kg-card-end: code--><p>In our case the application has to run as a <code>root</code> but in your case there might be a better user for that. Once created, you need to enable it:</p><!--kg-card-begin: code--><pre><code>$ 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</code></pre><!--kg-card-end: code--><h3 id="auto-deployment-service">Auto-deployment service</h3><p>Now we need a small script that will be periodically checking the remote repo:</p><!--kg-card-begin: code--><pre><code>#!/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
</code></pre><!--kg-card-end: code--><p>Similarily to the main application, we need to create a <code>systemd</code> service for the watcher:</p><!--kg-card-begin: code--><pre><code>[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</code></pre><!--kg-card-end: code--><p>and enable it as well:</p><!--kg-card-begin: code--><pre><code>$ 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</code></pre><!--kg-card-end: code--><h3 id="done">Done</h3><p>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 :)</p>]]></content:encoded></item><item><title><![CDATA[Squeezing Tasmota into 512KB ESP8266 (ESP-12) module]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p><a href="https://tasmota.github.io/docs/">Tasmota</a> is a custom firmware for ESP8266/ESP32 devices, used mostly as a replacement for the stock firmware in a <a href="https://templates.blakadder.com/">huge amount</a> of Internet connected home gadgets. It makes it very easy to connect them with automation software like <a href="https://www.home-assistant.io/">HomeAssistant</a>, <a href="https://www.openhab.org/">openHAB</a> or <a href="https://www.domoticz.com/">Domoticz</a>.</p>
<p>It comes with support for <a href="https://tasmota.github.io/docs/Supported-Peripherals/">a great</a></p>]]></description><link>https://suda.pl/squeezing-tasmota-on-512kb-esp8266-module/</link><guid isPermaLink="false">5ff308b85387260001c52b99</guid><category><![CDATA[esp8266]]></category><category><![CDATA[esp12]]></category><category><![CDATA[tasmota]]></category><category><![CDATA[espressif]]></category><category><![CDATA[embedded]]></category><category><![CDATA[platformio]]></category><dc:creator><![CDATA[Wojtek Siudzinski]]></dc:creator><pubDate>Mon, 04 Jan 2021 13:27:16 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p><a href="https://tasmota.github.io/docs/">Tasmota</a> is a custom firmware for ESP8266/ESP32 devices, used mostly as a replacement for the stock firmware in a <a href="https://templates.blakadder.com/">huge amount</a> of Internet connected home gadgets. It makes it very easy to connect them with automation software like <a href="https://www.home-assistant.io/">HomeAssistant</a>, <a href="https://www.openhab.org/">openHAB</a> or <a href="https://www.domoticz.com/">Domoticz</a>.</p>
<p>It comes with support for <a href="https://tasmota.github.io/docs/Supported-Peripherals/">a great number of peripherals</a> but this comes at a price: size. I still have some of the <a href="https://s.click.aliexpress.com/e/_Ad6ZYf">ESP-12</a> modules with only 512KB (4Mbit) of Flash. Tasmota comes in <a href="http://ota.tasmota.com/tasmota/release">multiple flavors</a> depending on what you need:</p>
<ul>
<li>Recommended binary with <em>most</em> drivers <strong>~587k</strong></li>
<li>Localized versions of the recommended binary which have similar size</li>
<li>HW specific ones like KNX, Sonoff Zigbee Bridge or IR support</li>
<li>Minimal which is only <strong>~379k</strong> in size but should not be used by itself as it's basically a bootstrap for OTA</li>
<li>Lite which just has some basic drivers and sensors support in <strong>~477k</strong></li>
</ul>
<p>The Lite version sounded like a perfect fit (quite literally) so I flashed a module with it and everything seemed fine. Until I tried to save the configuration. It seemed that every time it restarted, the default configuration was loaded instead of one I set. Looking at logs confirmed it:</p>
<pre><code>load 0x4010f000, len 3456, room 16
tail 0
chksum 0x84
csum 0x84
v372a3ec2
~ld
   00:00:00 CFG: Use defaults
00:00:00 Project tasmota Tasmota Version 8.2.0(lite)-STAGE
</code></pre>
<p>Searching the Tasmota GitHub issues, I found <a href="https://github.com/arendst/Tasmota/issues/8167">I wasn't the only one</a>. It seems that the binaries are linked for a bigger flash and I assume that the config area is outside of the actual flash therefore any write there would immediately get lost.</p>
<p>Following advice of Adrian Scillato I decided on building my own binary that would fit in the 512KB that I have. Here are the steps if you want to build your own:</p>
<ol start="0">
<li>Prepare the Tasmota <a href="https://tasmota.github.io/docs/Compile-your-build/">compiling tools</a>. Doesn't matter which one as long as you're able to edit the source code</li>
<li>Download the <a href="https://gist.githubusercontent.com/suda/ee5310f798c7c6dc06472f7a1b8fce42/raw/1852b569b1f5247efd53cccd7cab68f9486b9d56/user_config_override.h">custom config</a> into the <code>/tasmota</code> directory inside of Tasmota sources. This basically disables everything except light/dimmer, web UI and HomeAssistant support. Should use only ~87% of the flash, leaving the rest for config</li>
<li>Update the <code>platformio.ini</code> file to use the smaller flash:</li>
</ol>
<pre><code class="language-ini">[common]
board                       = esp01
board_build.ldscript        = eagle.flash.512k.ld
</code></pre>
<ol start="3">
<li>Compile the binary and <a href="https://tasmota.github.io/docs/Getting-Started/#flashing">flash it</a> to the device</li>
</ol>
<p>In case you don't want to go through building your own binary, here's my <code>ultralite</code> build of Tasmota 9.2.0: <a href="https://bit.ly/3b9WZJE"><code>tasmota-9.2.0-ultralite.bin</code></a>. I would still advise you to compile your own as it's not very secure to trust unknown binaries ;)</p>
<p>Following an ask by Michelle, I also made a build that includes the timers: <a href="https://suda-dropshare.s3-eu-west-1.amazonaws.com/tasmota-9.2.0-ultralite%2Btimers.bin"><code>tasmota-9.2.0-ultralite+timers.bin</code></a> :)</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Spinning up a free IPFS webrtc-star discovery server with Heroku]]></title><description><![CDATA[Deploy the webrtc-star signaling/discovery server in 5 minutes on Heroku for free]]></description><link>https://suda.pl/free-webrtc-star-heroku/</link><guid isPermaLink="false">5ecbde943831390001705d69</guid><category><![CDATA[ipfs]]></category><category><![CDATA[webrtc-star]]></category><category><![CDATA[websocket-star]]></category><category><![CDATA[js-ipfs]]></category><category><![CDATA[docker]]></category><category><![CDATA[heroku]]></category><dc:creator><![CDATA[Wojtek Siudzinski]]></dc:creator><pubDate>Mon, 25 May 2020 18:03:52 GMT</pubDate><media:content url="https://suda.pl/content/images/2020/05/Screenshot-from-2020-05-25-17-33-21.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="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"><p>While building the <strong><a href="https://webmic.suda.pl/">Web Microphone</a></strong> app with IPFS I sumbled upon an error when trying to use older examples but using the latest <code>js-ipfs</code>:</p>
<p><code>Error: no valid addresses were provided for transports [WebSockets,WebRTCStar,Circuit]</code></p>
<p>It got me puzzled but Vasco cleared it out in this thread:</p>
<!--kg-card-end: markdown--><!--kg-card-begin: embed--><figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><p lang="en" dir="ltr"><a href="https://twitter.com/ThePatToner?ref_src=twsrc%5Etfw">@ThePatToner</a> 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</p>&mdash; Vasco Santos (@vascosantos10) <a href="https://twitter.com/vascosantos10/status/1262769647482482689?ref_src=twsrc%5Etfw">May 19, 2020</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</figure><!--kg-card-end: embed--><!--kg-card-begin: markdown--><p>It turns out that it's necessary to use a new discovery mechanism, <a href="https://github.com/libp2p/js-libp2p-webrtc-star"><code>webrtc-star</code></a>. 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:</p>
<blockquote>
<p>it <strong>should not be used for apps in production</strong></p>
</blockquote>
<p>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:</p>
<h3 id="instructions">Instructions</h3>
<p>First install the <a href="https://devcenter.heroku.com/articles/heroku-cli">Heroku CLI</a> and then run:</p>
<pre><code class="language-sh"># Login to Heroku
$ heroku login
# Login to the Container Registry
$ heroku container:login
# Clone the webrtc-star repo
$ git clone https://github.com/libp2p/js-libp2p-webrtc-star.git
$ cd js-libp2p-webrtc-star
# Create a Heroku app
$ heroku create
# Build and push the image
$ heroku container:push web
# Release the image to your app
$ heroku container:release web
# Scale to one free worker
$ heroku ps:scale web=1
# Open the app in the browser
$ heroku open
</code></pre>
<p>Now the server should welcome you with its address:</p>
<p><img src="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"></p>
<p>Use this address in your IPFS app and you're done!</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Go for Particle Argon, Boron or Xenon]]></title><description><![CDATA[Thanks to TinyGo, "a Go compiler for tiny places", you can now use Go to write firmware for all 3rd gen Particle devices.]]></description><link>https://suda.pl/go-on-particle-argon-boron-or-xenon/</link><guid isPermaLink="false">5e8c40b3d2bb7100011709f7</guid><category><![CDATA[Particle]]></category><category><![CDATA[Particle Argon]]></category><category><![CDATA[Particle Boron]]></category><category><![CDATA[Particle Xenon]]></category><category><![CDATA[OpenOCD]]></category><category><![CDATA[TinyGo]]></category><category><![CDATA[Go]]></category><category><![CDATA[Golang]]></category><category><![CDATA[Particle Debugger]]></category><category><![CDATA[Particle Workbench]]></category><category><![CDATA[nRF52]]></category><dc:creator><![CDATA[Wojtek Siudzinski]]></dc:creator><pubDate>Thu, 16 Apr 2020 13:28:57 GMT</pubDate><media:content url="https://suda.pl/content/images/2020/04/Argon_tinygo.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://suda.pl/content/images/2020/04/Argon_tinygo.png" alt="Go for Particle Argon, Boron or Xenon"><p>With the release of <strong>TinyGo</strong> 0.13, you can now <strong>write firmware for Particle Argon, Boron and Xenon in Go</strong>:</p>
<!--kg-card-end: markdown--><!--kg-card-begin: embed--><figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">We just released version 0.13! Go 1.14 + LLVM 10 + support for 2^5 boards like the <a href="https://twitter.com/particle?ref_src=twsrc%5Etfw">@particle</a> <a href="https://twitter.com/hashtag/argon?src=hash&amp;ref_src=twsrc%5Etfw">#argon</a> + <a href="https://twitter.com/hashtag/WebAssembly?src=hash&amp;ref_src=twsrc%5Etfw">#WebAssembly</a> updates + so much more. Massive thank you to our amazing contributors!<a href="https://t.co/OJC4FBnsKk">https://t.co/OJC4FBnsKk</a><a href="https://twitter.com/hashtag/golang?src=hash&amp;ref_src=twsrc%5Etfw">#golang</a> <a href="https://twitter.com/hashtag/llvm?src=hash&amp;ref_src=twsrc%5Etfw">#llvm</a> <a href="https://twitter.com/hashtag/tinygo?src=hash&amp;ref_src=twsrc%5Etfw">#tinygo</a> <a href="https://twitter.com/hashtag/wasm?src=hash&amp;ref_src=twsrc%5Etfw">#wasm</a> <a href="https://twitter.com/hashtag/microcontrollers?src=hash&amp;ref_src=twsrc%5Etfw">#microcontrollers</a> <a href="https://twitter.com/hashtag/embedded?src=hash&amp;ref_src=twsrc%5Etfw">#embedded</a></p>&mdash; TinyGo (@TinyGolang) <a href="https://twitter.com/TinyGolang/status/1250125364321533952?ref_src=twsrc%5Etfw">April 14, 2020</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</figure><!--kg-card-end: embed--><!--kg-card-begin: markdown--><p>There are some caveats for now, namely:</p>
<ol>
<li>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 &quot;stock&quot; but it requires manual flashing of all necessary parts.</li>
<li>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 <a href="https://github.com/aykevl/go-bluetooth/pull/2#issuecomment-595768670">nRF Soft Device</a> that allows BLE support (and Mesh in the future) but it's still experimental.</li>
<li>You need to use the <a href="https://store.particle.io/products/particle-debugger">Particle Debugger</a> to do all the flashing. It might be possible to use <a href="https://github.com/adafruit/Adafruit_nRF52_Bootloader">Adafruit nRF52 Bootloader</a> (same as used for CircuitPython) in the future to allow flashing over the USB.</li>
</ol>
<p>That being said, running TinyGo might be a nice alternative to <a href="https://docs.particle.io/tutorials/learn-more/xenon-circuit-python/">CircuitPython</a> 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:</p>
<!--kg-card-end: markdown--><!--kg-card-begin: embed--><figure class="kg-card kg-embed-card"><iframe width="480" height="270" src="https://www.youtube.com/embed/uWK5jyE0Rm4?start=1725&feature=oembed" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></figure><!--kg-card-end: embed--><!--kg-card-begin: markdown--><p>Or drive six 32x32 RGB LED matrices on Cortex-M4:</p>
<!--kg-card-end: markdown--><!--kg-card-begin: embed--><figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">Do you want to make your own? The code is open source! &gt;&gt; <a href="https://t.co/b8wnO6Qu30">https://t.co/b8wnO6Qu30</a> <a href="https://twitter.com/TinyGolang?ref_src=twsrc%5Etfw">@TinyGolang</a> <a href="https://t.co/zMayDQdwtW">pic.twitter.com/zMayDQdwtW</a></p>&mdash; conejo 🐇🐰⚡ (@_CONEJO) <a href="https://twitter.com/_CONEJO/status/1238889662804045826?ref_src=twsrc%5Etfw">March 14, 2020</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</figure><!--kg-card-end: embed--><!--kg-card-begin: markdown--><p>Additionally, a strong advantage of TinyGo is that <strong>TinyGo is written in Go</strong> itself! Meaning, you don't need to know C/C++ to understand/contribute or write low level code.</p>
<p>Got you interested? Lets proceed to the...</p>
<h1 id="instructions">Instructions</h1>
<h2 id="prerequisites">Prerequisites</h2>
<ul>
<li><a href="https://store.particle.io/collections/bluetooth">Particle Argon/Boron/Xenon</a> $11-$55</li>
<li><a href="https://store.particle.io/products/particle-debugger">Particle Debugger</a> $12</li>
</ul>
<h2 id="necessarysoftware">Necessary software</h2>
<ol>
<li>Install OpenOCD. If you have <a href="https://www.particle.io/workbench/">Particle Workbench</a> installed, it should already download it for you to <code>~/.particle/toolchains/openocd</code> directory. Make sure the <code>openocd</code> binary is in your <code>PATH</code>.</li>
<li><a href="https://tinygo.org/getting-started/">Install TinyGo</a></li>
</ol>
<h2 id="blinky">Blinky</h2>
<p>Well, that's it really. Now you're ready to go write Go:</p>
<pre><code class="language-go">package main

import (
	// machine package exposes all hardware available on the microcontroller
	&quot;machine&quot;
	&quot;time&quot;
)

func main() {
	// All the pins are listed here: https://tinygo.org/microcontrollers/machine/particle-xenon/
	led := machine.LED
	// Make it an output
	led.Configure(machine.PinConfig{Mode: machine.PinOutput})
	// Repeat forever
	for {
		// Turn it on (it's active low)
		led.Low()
		time.Sleep(time.Millisecond * 500)

		// And off
		led.High()
		time.Sleep(time.Millisecond * 500)
	}
}
</code></pre>
<p>Now you can flash your app with:</p>
<pre><code class="language-sh">$ tinygo flash -target=particle-xenon blink
</code></pre>
<p>(assuming you save the Go code above in <code>blink</code> directory). Also, you should set the <code>-target</code> flag to the module you're using.</p>
<h1 id="summary">Summary</h1>
<p>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 <a href="https://github.com/tinygo-org/drivers">plenty of drivers</a> for devices like displays, sensors, etc.</p>
<p>I hope you'll have fun with TinyGo and if you like to chat about it or contribute, feel free to join the <a href="https://gophers.slack.com/messages/CDJD3SUP6/">Slack channel</a> and check out the <a href="https://github.com/tinygo-org">GitHub repos</a>.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Single Dockerfile for testing and production using multi-stage builds]]></title><description><![CDATA[Create a single Dockerfile to build development, testing and production images.]]></description><link>https://suda.pl/single-dockerfile-for-testing-and-production/</link><guid isPermaLink="false">5e7dd506d2bb710001170911</guid><category><![CDATA[docker]]></category><category><![CDATA[multi-stage]]></category><category><![CDATA[dockerfile]]></category><category><![CDATA[python]]></category><dc:creator><![CDATA[Wojtek Siudzinski]]></dc:creator><pubDate>Fri, 27 Mar 2020 11:11:07 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1531206715517-5c0ba140b2b8?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://images.unsplash.com/photo-1531206715517-5c0ba140b2b8?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjExNzczfQ" alt="Single Dockerfile for testing and production using multi-stage builds"><p>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 <strong>definitely</strong> don't want on production.</p>
<p>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.</p>
<p>Fortunately since Docker 17.05 we have the concept of <a href="https://docs.docker.com/develop/develop-images/multistage-build/#use-a-previous-stage-as-a-new-stage">multi-stage builds</a>. An ability to declare separate images in one <code>Dockerfile</code> and freely inherit or copy data between each other. This is the exact approach I used when building images for <a href="https://lox.sh/?source=suda.pl">Lox</a>:</p>
<pre><code class="language-Dockerfile">#############################################
# Base container with all necessary deps
FROM tiangolo/uvicorn-gunicorn:python3.7-alpine3.8 AS base

ENV HOME=/app 
    BUILD_DEPS=&quot;build-base linux-headers postgresql postgresql-dev libffi-dev&quot;
WORKDIR ${HOME}

# Copy the pipenv files
COPY Pipfile ${WORKDIR}/
COPY Pipfile.lock ${WORKDIR}/

# 1. Install system dependencies
# 2. Install pipenv
# 3. Use pipenv to install app deps
# 4. Remove system deps to save space
RUN apk add --no-cache ${BUILD_DEPS} \
    &amp;&amp; pip install --no-cache-dir pipenv \
    &amp;&amp; pipenv install --system \
    &amp;&amp; apk del ${BUILD_DEPS}


#############################################
# Test container from a common base
FROM base AS test
# Same as in the base image but this time we also install the --dev packages
RUN apk add --no-cache ${BUILD_DEPS} \
    &amp;&amp; pip install --no-cache-dir pipenv \
    &amp;&amp; pipenv install --system --dev \
    &amp;&amp; apk del ${BUILD_DEPS}


#############################################
# Live container with Webpack watcher
FROM base AS live
# Install Node.js dependencies
RUN apk add --no-cache nodejs npm
# Install all Webpack dependencies
COPY package.json package-lock.json ./
RUN npm install
# Finally copy the current sources and build the bundle
COPY . .
RUN npm run bundle:build


#############################################
# Final container with the app
FROM base AS production
# Only copy the ready app dir from the live step
COPY --from=live ${WORKDIR} ${WORKDIR}
</code></pre>
<p><strong>Note:</strong> for the sake of brevity, this <code>Dockerfile</code> doesn't contain all size improvements and will produce slightly bigger images.</p>
<p>Now you're able to build separate images for each of your needs:</p>
<h3 id="testing">Testing</h3>
<pre><code class="language-sh">$ docker build -t suda/lox/test --target test .
</code></pre>
<h3 id="developmentwlivereload">Development w/ live-reload</h3>
<pre><code class="language-sh">$ docker build -t suda/lox/live --target live .
</code></pre>
<h3 id="production">Production</h3>
<pre><code class="language-sh">$ docker build -t suda/lox --target production .
</code></pre>
<p><strong>P.S.:</strong> You might've noticed I used the great Python base image from <a href="https://github.com/tiangolo">Sebastián Ramírez</a> and if you want to deploy a Django project using ASGI, <a href="https://suda.pl/deploying-django-3-asgi/">I have some instructions that can help you</a>.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Production-ready Django 3 ASGI Docker image]]></title><description><![CDATA[A boilerplate image for deploying ASGI Django applications on production using uvicorn and WhiteNoise.]]></description><link>https://suda.pl/deploying-django-3-asgi/</link><guid isPermaLink="false">5e0a8b397d27530001e596b7</guid><category><![CDATA[django]]></category><category><![CDATA[asgi]]></category><category><![CDATA[docker]]></category><category><![CDATA[async]]></category><category><![CDATA[collectstatic]]></category><category><![CDATA[uvicorn]]></category><dc:creator><![CDATA[Wojtek Siudzinski]]></dc:creator><pubDate>Thu, 02 Jan 2020 16:49:49 GMT</pubDate><media:content url="https://suda.pl/content/images/2020/01/spacex-OHOU-5UVIYQ.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://suda.pl/content/images/2020/01/spacex-OHOU-5UVIYQ.jpg" alt="Production-ready Django 3 ASGI Docker image"><p></p><!--kg-card-begin: markdown--><p>With <a href="https://docs.djangoproject.com/en/3.0/releases/3.0/#asgi-support">Django 3 released</a>, it's a great moment to jump in on all the async goodies it provides. Unfortunately for me, it means dropping my <a href="https://suda.pl/deploying-django-with-uwsgi/">uWSGI Docker config</a> and figuring out a new approach.</p>
<p>Fortunately, Sebastián Ramírez of <code>fastapi</code> fame, created a very nice base image using <code>uvicorn</code>: <a href="https://github.com/tiangolo/uvicorn-gunicorn-docker">uvicorn-gunicorn-docker</a>. One thing missing was the ability to serve static files, but here's where <a href="http://whitenoise.evans.io/en/stable/base.html"><code>WhiteNoise</code></a> comes in.</p>
<p>Let's jump in to the setup.</p>
<h1 id="instructions">Instructions</h1>
<p>Compared to my <a href="https://suda.pl/deploying-django-with-uwsgi/">uWSGI setup</a>, here we just need to create a <code>Dockerfile</code>:</p>
<pre><code class="language-ini">ARG PYTHON_VERSION=3.7

# Build dependencies in separate container
FROM tiangolo/uvicorn-gunicorn:python${PYTHON_VERSION}-alpine3.8 AS builder
ENV WORKDIR /app
COPY Pipfile ${WORKDIR}/
COPY Pipfile.lock ${WORKDIR}/

RUN cd ${WORKDIR} \
    &amp;&amp; pip install pipenv \
    &amp;&amp; pipenv install --system

# Create the final container with the app
FROM tiangolo/uvicorn-gunicorn:python${PYTHON_VERSION}-alpine3.8

ENV USER=docker \
    GROUP=docker \
    UID=12345 \
    GID=23456 \
    HOME=/app \
    PYTHONUNBUFFERED=1
WORKDIR ${HOME}

# Create user/group
RUN addgroup --gid &quot;${GID}&quot; &quot;${GROUP}&quot; \
    &amp;&amp; adduser \
    --disabled-password \
    --gecos &quot;&quot; \
    --home &quot;$(pwd)&quot; \
    --ingroup &quot;${GROUP}&quot; \
    --no-create-home \
    --uid &quot;${UID}&quot; \
    &quot;${USER}&quot;

# Run as docker user
USER ${USER}
# Copy installed packages
COPY --from=builder /usr/local/lib/python3.7/site-packages /usr/local/lib/python3.7/site-packages
# Copy the application
COPY --chown=docker:docker . .
# Collect the static files
RUN python manage.py collectstatic --noinput
</code></pre>
<h2 id="servingthestaticfiles">Serving the static files</h2>
<p>For this purpouse, I'm using <a href="http://whitenoise.evans.io/en/stable/base.html"><code>WhiteNoise</code></a> which needs to be added to dependencies:</p>
<pre><code class="language-sh">$ pipenv install whitenoise
</code></pre>
<p>and then added <code>whitenoise.middleware.WhiteNoiseMiddleware</code> to the top of the <code>MIDDLEWARE</code> array in <code>settings.py</code>, right below the <code>SecurityMiddleware</code>:</p>
<pre><code class="language-python">MIDDLEWARE = [
  'django.middleware.security.SecurityMiddleware',
  'whitenoise.middleware.WhiteNoiseMiddleware',
  # ...
]
</code></pre>
<p><strong>Side note:</strong> I know many people are wondering why I'm so set on serving static files, instead of just using S3 buit I think <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">David explained it well</a>:</p>
<blockquote>
<p><strong>Shouldn’t I be pushing my static files to S3 using something like Django-Storages?</strong><br>
No. (...) problem with a push-based approach to handling static files is that it adds complexity and fragility to your deployment process.</p>
</blockquote>
<h2 id="runningtheimage">Running the image</h2>
<p><code>uvicorn</code> needs to know what is the module is the main application, therefore we need to set the <code>APP_MODULE</code> environment variable to <code>myapp.asgi:application</code> replacing <code>myapp</code> with the name of your app. This assumes you have the <code>asgi.py</code> file (ASGI equivalent of the <code>wsgi.py</code>) 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:</p>
<pre><code class="language-python">&quot;&quot;&quot;
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/
&quot;&quot;&quot;

import os

from django.core.asgi import get_asgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myapp.settings')

application = get_asgi_application()

</code></pre>
<h1 id="notes">Notes</h1>
<p>Running in <a href="https://docs.djangoproject.com/en/3.0/topics/async/">async mode</a>, means you should not use possibly blocking, synchronous methods (like the ORM) in the main thread. <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">Quick tip</a> here is, that if you get <code>SynchronousOnlyOperation</code> exception you might want to wrap it with <code>sync_to_async</code>:</p>
<pre><code class="language-python">from asgiref.sync import sync_to_async

def my_db_function():
    &lt;do orm stuff&gt;

await sync_to_async(my_db_function)()
</code></pre>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[React/Flux like pattern for embedded UI]]></title><description><![CDATA[Data flow pattern for C/C++ inspired by React/Flux]]></description><link>https://suda.pl/react-like-pattern-for-embedded-ui/</link><guid isPermaLink="false">5dc021cc24f83100016ae370</guid><category><![CDATA[React]]></category><category><![CDATA[Particle]]></category><category><![CDATA[Xenon]]></category><category><![CDATA[ps-01]]></category><category><![CDATA[Flux]]></category><dc:creator><![CDATA[Wojtek Siudzinski]]></dc:creator><pubDate>Thu, 21 Nov 2019 17:23:00 GMT</pubDate><media:content url="https://suda.pl/content/images/2019/11/flux.001.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://suda.pl/content/images/2019/11/flux.001.png" alt="React/Flux like pattern for embedded UI"><p>When building desktop applications, whether it's with native frameworks or with web ones, we take for granted how easy it is to handle. I'm in the process of building a portable synthesizer that runs on Particle <a href="https://store.particle.io/collections/bluetooth/products/xenon">Xenon</a> with limited resources and on a very slow SPI screen which made me appreciate web frameworks a bit more.</p>
<h2 id="theproblem">The problem</h2>
<p>The problem I had, boiled down to figuring out a straightforward (and fast) way to reconcile many inputs/outputs.</p>
<p><img src="http://suda-dropshare.s3-eu-west-1.amazonaws.com/flux.002.png" alt="React/Flux like pattern for embedded UI"></p>
<p>There are many ways to go about it but I wanted a clear and straightforward way of describing <strong>what happened</strong> and <strong>how to react</strong>. The last word of the previous sentence might give you some indication about what I went with ;)</p>
<h2 id="reactandflux">React and Flux</h2>
<p><a href="https://reactjs.org/">React</a> is a popular frontend library, enabling reusable, reactive components for the user interface. <a href="https://facebook.github.io/flux/">Flux</a> is an architecture of how events should flow through the system, that complements React. Note that Flux isn't a library but rather a concept that libraries like <a href="https://redux.js.org/">Redux</a> took and implemented. But for the purpouse of this project, Flux was simple enough.</p>
<p>Just to be clear, I didn't implement React/Flux in C but rather used some of the principles to develop a pattern. From React, I took partial rendering of only the things that changed and from Flux the idea of actions, stores and dispatchers.</p>
<h3 id="actionsstoresviewsanddispatchers">Actions, stores, views and dispatchers</h3>
<p><img src="http://suda-dropshare.s3-eu-west-1.amazonaws.com/flux.005.png" alt="React/Flux like pattern for embedded UI"></p>
<p>There's a great explanation of all those principles in <a href="https://facebook.github.io/flux/docs/in-depth-overview">Flux's documentation</a>. I made some adjustments, mostly to accomodate limitations of the microcontroller and statically typed C. What I ended up with is:</p>
<h4 id="actions">Actions</h4>
<p>To identify each <em>Action</em>, they are being defined with unique identifier, so when they are dipatched, they can be passed to the correct <em>View</em>.</p>
<pre><code class="language-c">#define ACTION_INCREMENT_COUNTER 1
</code></pre>
<h4 id="stores">Stores</h4>
<p>A <em>Store</em> (rather than many stores) was implemented as a <code>struct</code>:</p>
<pre><code class="language-c">struct Store {
  uint32_t counter;
}

#define STORE_PROP_COUNTER 1
</code></pre>
<p>The <em>Store</em> is being shared between <em>Views</em> mostly to allow mixing global state with local one. Additionally this allows a easy way to serialize/deserialize and store it in EEPROM.</p>
<p>For each <em>Store</em> property there's a <code>#define</code> that is used to indicate which property has been changed.</p>
<h4 id="views">Views</h4>
<p>A <em>View</em> is just a class that implements two methods:</p>
<ul>
<li><code>void handleAction(uint8_t action, int16_t args[])</code></li>
<li><code>void handleStoreUpdate(uint8_t storeProp)</code></li>
</ul>
<p>If the <em>View</em> is currently active (there's an assumption that only one <em>View</em> can be active at the time), it will receive all the dispatched <em>Actions</em>. The <code>handleAction</code> method should decide if it will react to it. If it does, it can dispatch another <em>Action</em> or modify the <em>Store</em>. If it does update the <em>Store</em>, it should call <code>handleStoreUpdate()</code> with respective <em>Store</em> property, to update the UI.</p>
<p>An example action handler might look like this:</p>
<pre><code class="language-c">void MyView::handleAction(uint8_t action, int16_t args[]) {
  switch (action) {
    // For the increment action...
    case ACTION_INCREMENT_COUNTER:
      // increment the counter in the store by the first argument
      this-&gt;store-&gt;counter += args[0];
      // Update the UI that displays the counter
      this-&gt;handleStoreUpdate(STORE_PROP_COUNTER);
      break;
  }
}
</code></pre>
<p>and the update handler like this:</p>
<pre><code class="language-c">void MyView::handleStoreUpdate(uint8_t storeProp) {
  switch (storeProp) {
    // For the counter property...
    case STORE_PROP_COUNTER:
      // ..update the UI
      this-&gt;screen-&gt;drawCounter();
      break;
  }
}
</code></pre>
<h4 id="dispatchers">Dispatchers</h4>
<p>A <em>Dispatcher</em> is a main class that contains the list of <em>Views</em> including which of them is currently active and routes all dispatched <em>Actions</em> to it.<br>
Actions can take arguments and for simplicity, it's an array of four <code>int16_t</code>. To make calling easier, there are two macros to help:</p>
<pre><code class="language-c">#define MK_ARGS(name, arg0, arg1, arg2, arg3) int16_t name[] = {arg0, arg1, arg2, arg3};
#define NO_ARGS(name) MK_ARGS(name, 0, 0, 0, 0)
</code></pre>
<p>the idea behind this is to change the macros if the type (or number) of arguments changes instead of changing each piece of code that dispatches an <em>Action</em>.</p>
<p>To dispatch an <em>Action</em> the <code>dispatchAction()</code> method has to be called:</p>
<pre><code class="language-c">MK_ARGS(args, 1, 0, 0, 0)
dispatcher.dispatchAction(ACTION_INCREMENT_COUNTER, args);
</code></pre>
<p>all of this can be summarized in one, nifty chart:</p>
<p><img src="http://suda-dropshare.s3-eu-west-1.amazonaws.com/flux.010.jpeg" alt="React/Flux like pattern for embedded UI"></p>
<h2 id="wherenext">Where next?</h2>
<p>I developed the pattern above while working on my <a href="https://github.com/suda/ps-01"><code>ps-01</code> synthesizer</a> and it's the main place where a &quot;reference implementation&quot; lives.</p>
<p>I also made a video going through the early iteration of this pattern (where <em>View</em> is called a Page and <em>Dispatcher</em> an UI):</p>
<!--kg-card-end: markdown--><!--kg-card-begin: embed--><figure class="kg-card kg-embed-card"><iframe width="480" height="270" src="https://www.youtube.com/embed/dO7Qoi_7U4c?feature=oembed" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></figure><!--kg-card-end: embed--><!--kg-card-begin: markdown--><p>The slides from the video are also available on <a href="https://speakerdeck.com/suda/flux-like-pattern-for-embedded-ui">SpeakerDeck</a>.</p>
<p>If you have any comments or ideas about this pattern, please let me know in the comments (or on social media) 🙂</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[5-minute home server with microk8s and Rancher]]></title><description><![CDATA[Quick guide to microk8s and Rancher installation on Ubuntu 18.02 LTS]]></description><link>https://suda.pl/5-minute-home-server-with/</link><guid isPermaLink="false">5dc96f5f24f83100016ae545</guid><category><![CDATA[kubernetes]]></category><category><![CDATA[rancher]]></category><category><![CDATA[microk8s]]></category><category><![CDATA[ubuntu]]></category><category><![CDATA[helm]]></category><category><![CDATA[docker]]></category><dc:creator><![CDATA[Wojtek Siudzinski]]></dc:creator><pubDate>Mon, 11 Nov 2019 15:24:09 GMT</pubDate><media:content url="https://suda.pl/content/images/2019/11/Untitled.001-2.png" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://suda.pl/content/images/2019/11/Untitled.001-2.png" alt="5-minute home server with microk8s and Rancher"><p><strong>Updated on Jan 10, 2021</strong> to use Helm 3.</p>
<p>After getting tired with instability of my QNAP TS-251B (random reboots once a day, official apps not working) I decided to turn my Intel NUC into a home server. I could've used a Raspberry Pi, but I do need a x86 for most of the Docker images I use. I decided to use Kubernetes, which for many might come off as a huge overkill (and possibly it is) but that's what I'm interacting with in my professional life thus it's easier for me to do what I need in a familiar environment :)</p>
<p>I looked through single node Kubernetes installation options, I found <a href="https://microk8s.io/"><code>microk8s</code></a> which <a href="https://twitter.com/kelseyhightower/status/1120834594138406912">Kelsey Hightower called &quot;the easiest way to provision a single node Kubernetes cluster&quot;</a>. Oh boy was he right! Ubuntu Server comes with an option to install <code>microk8s</code> making it practically zero effort installation!</p>
<p>Second part for me was to add some GUI to manage. I choose <a href="https://rancher.com/products/rancher">Rancher</a> as it has a great integration with both Kubernetes and <code>kops</code>.</p>
<h1 id="installation">Installation</h1>
<p>First start with a fresh installation of <a href="https://ubuntu.com/download/server">Ubuntu Server</a>. I used 18.04 LTS but feel free to use latest version. To make a bootable USB drive with the image, you can use <a href="https://www.balena.io/etcher/">Balena Etcher</a>. Once you have it installed on your machine, ssh into it and issue following commands:</p>
<pre><code class="language-bash"># Install microk8s from the 1.19 channel (Rancher doesn't support Kubernetes 1.20 yet)
$ sudo snap install microk8s --classic --channel=1.19
# Enable useful plugins
$ sudo microk8s.enable dns dashboard storage ingress helm3

# Allow running priviledged Pods (required by Rancher's `cattle-node-agent`)
$ sudo sh -c 'echo &quot;--allow-privileged=true&quot; &gt;&gt; /var/snap/microk8s/current/args/kube-apiserver'
$ sudo systemctl restart snap.microk8s.daemon-apiserver.service

# Install cert-manager user by Rancher
$ sudo microk8s.helm3 repo add jetstack https://charts.jetstack.io
$ sudo microk8s.kubectl apply -f https://raw.githubusercontent.com/jetstack/cert-manager/release-0.9/deploy/manifests/00-crds.yaml
$ sudo microk8s.kubectl create namespace cert-manager
$ sudo microk8s.kubectl label namespace cert-manager certmanager.k8s.io/disable-validation=true
$ sudo microk8s.helm3 install --name cert-manager --namespace cert-manager --version v0.9.1 jetstack/cert-manager

# Install stable Rancher
$ sudo microk8s.kubectl create namespace cattle-system
$ sudo microk8s.kubectl label namespace cattle-system cattle-system.k8s.io/disable-validation=true
$ sudo microk8s.helm3 repo add rancher-latest https://releases.rancher.com/server-charts/latest
$ sudo microk8s.helm3 repo update
$ sudo microk8s.helm3 install rancher rancher-latest/rancher --namespace cattle-system  --set replicas=1 --set hostname=${HOSTNAME}.home
</code></pre>
<p>Now you should be able to see Rancher interface at <code>https://SERVER_IP</code>! There are two manual tasks you need to do on your machine:</p>
<ul>
<li>the SSL certificate will be marked as invalid so you'll need to add it to trusted certificates</li>
<li>Rancher is installed with a <code>${HOSTNAME}.home</code> hostname which you need to add to your <code>/etc/hosts</code> file (<code>.local</code> domain can't be used with Rancher)</li>
</ul>
<p>Hope this worked and let me know if you have any comments!</p>
<p>Thanks to Harald Löbig for helping with the Helm 3 update!</p>
<p>P.S: If you're using Raspberry Pi, the Rancher folk created <a href="https://k3s.io/"><code>k3s</code></a> which is a single node Kubernetes installation that requires &lt;512MB of RAM!</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Deploying Django to production with uWSGI]]></title><description><![CDATA[Production-ready Docker image for deploying Django with uWSGI, including serving the static files.]]></description><link>https://suda.pl/deploying-django-with-uwsgi/</link><guid isPermaLink="false">5e0df0157d27530001e596c4</guid><category><![CDATA[django]]></category><category><![CDATA[uwsgi]]></category><category><![CDATA[docker]]></category><category><![CDATA[deployment]]></category><category><![CDATA[collectstatic]]></category><category><![CDATA[docker-compose]]></category><category><![CDATA[pipenv]]></category><dc:creator><![CDATA[Wojtek Siudzinski]]></dc:creator><pubDate>Sun, 01 Sep 2019 12:28:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>There are many posts about dockerizing Django apps but I feel like there's some room for improvement.</p>
<p>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 <a href="https://medium.com/goglides/stop-running-an-application-inside-a-docker-container-as-the-root-user-f255a7810c0c">running the application as <code>root</code></a> or ignored handling static files and defered it to S3. With this images I had several goals:</p>
<ul>
<li>Production-ready image (following general security tips)</li>
<li>Using <code>alpine</code> base for a small image</li>
<li><a href="https://docs.docker.com/develop/develop-images/multistage-build/">Multi-stage</a> build for even smaller and cleaner image</li>
<li>Handling static files inside the same container</li>
</ul>
<p>I decided on using <a href="https://uwsgi-docs.readthedocs.io/en/latest/"><code>uWSGI</code></a> which actually also can serve static files, making my life much easier.</p>
<p><strong>Note:</strong> I'm using <a href="https://pipenv.readthedocs.io/en/latest/">pipenv</a> to manage virtualenv and all dependencies (and I recommend you use it too), therefore some instructions might need tweaking if you're using bare <code>pip</code>.</p>
<h1 id="instructions">Instructions</h1>
<h2 id="setupuwsgi">Setup <code>uWSGI</code></h2>
<p>Start by adding it to your dependencies:</p>
<pre><code class="language-sh">$ pipenv install uwsgi
</code></pre>
<p>Then create <code>uwsgi.ini</code> file in your project root directory:</p>
<pre><code class="language-ini">[uwsgi]
chdir = /app
uid = $(UID)
gid = $(GID)
module = $(UWSGI_MODULE)
processes = $(UWSGI_PROCESSES)
threads = $(UWSGI_THREADS)
procname-prefix-spaced = uwsgi:$(UWSGI_MODULE)

http-socket = :8080
http-enable-proxy-protocol = 1
http-auto-chunked = true
http-keepalive = 75
http-timeout = 75
stats = :1717
stats-http = 1
offload-threads = $(UWSGI_OFFLOAD_THREADS)

# Better startup/shutdown in docker:
die-on-term = 1
lazy-apps = 0

vacuum = 1
master = 1
enable-threads = true
thunder-lock = 1
buffer-size = 65535

# Logging
log-x-forwarded-for = true

# Avoid errors on aborted client connections
ignore-sigpipe = true
ignore-write-errors = true
disable-write-exception = true

no-defer-accept = 1

# Limits, Kill requests after 120 seconds
harakiri = 120
harakiri-verbose = true
post-buffering = 4096

# Custom headers
add-header = X-Content-Type-Options: nosniff
add-header = X-XSS-Protection: 1; mode=block
add-header = Strict-Transport-Security: max-age=16070400
add-header = Connection: Keep-Alive

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

# Cache stat() calls
cache2 = name=statcalls,items=30
static-cache-paths = 86400

# Redirect http -&gt; https
route-if = equal:${HTTP_X_FORWARDED_PROTO};http redirect-permanent:https://${HTTP_HOST}${REQUEST_URI}
</code></pre>
<p><strong>Note:</strong> The author of the config above is <a href="https://github.com/nginxinc/kubernetes-ingress/issues/143#issuecomment-347814243">Diederik van der Boor</a> 🙇</p>
<h2 id="createthedockerfile">Create the <code>Dockerfile</code></h2>
<pre><code class="language-ini"># Build argument allowing to change Python version
ARG PYTHON_VERSION=3.7

# Build dependencies in separate container
FROM python:${PYTHON_VERSION}-alpine AS builder
ENV WORKDIR /app
COPY Pipfile ${WORKDIR}/
COPY Pipfile.lock ${WORKDIR}/

RUN cd ${WORKDIR} \
    &amp;&amp; pip install pipenv \
    &amp;&amp; pipenv install --system

# Create the final container with the app
FROM python:${PYTHON_VERSION}-alpine

ENV USER=docker \
    GROUP=docker \
    UID=12345 \
    GID=23456 \
    HOME=/app \
    PYTHONUNBUFFERED=1
WORKDIR ${HOME}

# Create user/group
RUN addgroup --gid &quot;${GID}&quot; &quot;${GROUP}&quot; \
    &amp;&amp; adduser \
    --disabled-password \
    --gecos &quot;&quot; \
    --home &quot;$(pwd)&quot; \
    --ingroup &quot;${GROUP}&quot; \
    --no-create-home \
    --uid &quot;${UID}&quot; \
    &quot;${USER}&quot;

# Run as docker user
USER ${USER}
# Copy installed packages
COPY --from=builder /usr/local/lib/python3.7/site-packages /usr/local/lib/python3.7/site-packages
# Copy uWSGI binary
COPY --from=builder /usr/local/bin/uwsgi /usr/local/bin/uwsgi
# Copy the application
COPY --chown=docker:docker . .
# Collect static files
RUN python manage.py collectstatic --noinput

ENTRYPOINT [ &quot;uwsgi&quot;, &quot;--ini&quot;, &quot;uwsgi.ini&quot; ]
EXPOSE 8080
</code></pre>
<h2 id="startingthecontainer">Starting the container</h2>
<p>To work correctly, you need to set some environment variables. If you're using Docker Compose, you could use similar <code>docker-compose.yml</code> file:</p>
<pre><code class="language-yaml">version: &quot;3&quot;
services:
  app:
    build: .
    image: foo/bar
    ports:
      - &quot;8080:8080&quot;
    environment:
      - UWSGI_MODULE=myapp.wsgi:application
      - UWSGI_PROCESSES=10
      - UWSGI_THREADS=2
      - UWSGI_OFFLOAD_THREADS=10
      - UWSGI_STATIC_EXPIRES=86400
</code></pre>
<p>then you can build the image with following command:</p>
<pre><code class="language-sh">$ docker-compose build app
</code></pre>
<h1 id="conclusion">Conclusion</h1>
<p>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!</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Moving Kubernetes Persistent Volumes within the same cloud account/cluster]]></title><description><![CDATA[<p></p><!--kg-card-begin: markdown--><p>During restructuring of my <a href="https://www.digitalocean.com/products/kubernetes/?refcode=fef9487dad1e&amp;utm_campaign=Referral_Invite&amp;utm_medium=Referral_Program&amp;utm_source=CopyPaste">Digital Ocean Kubernetes cluster</a> I needed to move some Persistent Volumes across namespace and some of them across the clusters.</p>
<p>One approach was to manually copy all the data using <a href="https://github.com/suda/dvsync"><code>dvsync</code> script</a> but fortunately both clusters existed under the same Digital Ocean account which meant it</p>]]></description><link>https://suda.pl/moving-kubernetes-persistent-volumes-within-the-same-cloud-account-cluster/</link><guid isPermaLink="false">5cd2ef147f28de0001c77c8e</guid><category><![CDATA[kubernetes]]></category><category><![CDATA[aws]]></category><category><![CDATA[ebs]]></category><category><![CDATA[eks]]></category><category><![CDATA[kubectl]]></category><category><![CDATA[Persistent Volume]]></category><category><![CDATA[Persistent Volume Claim]]></category><category><![CDATA[persistentVolumeReclaimPolicy]]></category><category><![CDATA[Digital Ocean]]></category><dc:creator><![CDATA[Wojtek Siudzinski]]></dc:creator><pubDate>Wed, 08 May 2019 15:52:16 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1510681916233-314f497f3301?ixlib=rb-1.2.1&amp;q=80&amp;fm=jpg&amp;crop=entropy&amp;cs=tinysrgb&amp;w=1080&amp;fit=max&amp;ixid=eyJhcHBfaWQiOjExNzczfQ" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1510681916233-314f497f3301?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=1080&fit=max&ixid=eyJhcHBfaWQiOjExNzczfQ" alt="Moving Kubernetes Persistent Volumes within the same cloud account/cluster"><p></p><!--kg-card-begin: markdown--><p>During restructuring of my <a href="https://www.digitalocean.com/products/kubernetes/?refcode=fef9487dad1e&amp;utm_campaign=Referral_Invite&amp;utm_medium=Referral_Program&amp;utm_source=CopyPaste">Digital Ocean Kubernetes cluster</a> I needed to move some Persistent Volumes across namespace and some of them across the clusters.</p>
<p>One approach was to manually copy all the data using <a href="https://github.com/suda/dvsync"><code>dvsync</code> script</a> but fortunately both clusters existed under the same Digital Ocean account which meant it should be possible to reuse already created DO volume.</p>
<p><strong>Note:</strong> this approach should work as well with Amazon AWS, Google Cloud GKS and Azure AKS.</p>
<p>Here are the steps I took:</p>
<h4 id="firstmakesureremovingpvwontremovetheunderlyingvolume">First make sure removing PV won't remove the underlying volume</h4>
<p>By default, the Persistent Volumes have <a href="https://kubernetes.io/docs/concepts/storage/persistent-volumes/#reclaiming"><code>persistentVolumeReclaimPolicy</code></a> set to <code>Delete</code>. This would automatically delete the underlying EBS/DO volume. We can change it to <code>Retain</code> by patching the PV:</p>
<pre><code class="language-shell">$ kubectl patch pv ${PV_NAME} -p '{&quot;spec&quot;:{&quot;persistentVolumeReclaimPolicy&quot;:&quot;Retain&quot;}}'
</code></pre>
<h4 id="thenexportbothpersistentvolumeanditsclaim">Then export both Persistent Volume and its Claim</h4>
<pre><code class="language-shell">$ kubectl get pv/${PV_NAME} --export -o yaml &gt; ${PV_NAME}.yaml
$ kubectl get pvc/${PVC_NAME} --export -o yaml &gt; ${PVC_NAME}.yaml
</code></pre>
<h4 id="createallnecessaryobjectsinthenewcluster">Create all necessary objects in the new cluster</h4>
<p>If you're using <a href="https://helm.sh">Helm</a> to manage deployments and create PVC's, you need to run it first and allow it to create the PVC on its own.<br>
After this, delete the created PVC (you'll replace it with the exported one in the next step).</p>
<pre><code class="language-shell">$ kubectl delete pvc/${PVC_NAME}
</code></pre>
<p><strong>Note:</strong> this will also delete the new PV due to default <code>persistentVolumeReclaimPolicy</code> mentioned above.</p>
<h4 id="inserttheexportedpvandpvc">Insert the exported PV and PVC</h4>
<p>Kubernetes uses <code>UID</code> to determine connection between PVC and PV therefore you'll need to patch the PV with correct one:</p>
<pre><code class="language-shell">$ kubectl apply -f ${PVC_NAME}.yaml
$ PVC_UID=$(kubectl get pvc/${PVC_NAME} -o jsonpath='{.metadata.uid}')
$ # If you're operating in the same cluster, you don't need to do the next step
$ kubectl apply -f ${PV_NAME}.yaml
$ kubectl patch pv ${PV_NAME} -p &quot;{\&quot;spec\&quot;:{\&quot;claimRef\&quot;:{\&quot;uid\&quot;:\&quot;${PVC_UID}\&quot;}}}&quot;
</code></pre>
<h4 id="stopremovetheolddeployment">Stop/remove the old deployment</h4>
<p>You won't be able to use this volume in the new cluster as long it is attached to the old node, so you need to remove the old PV/PVC.</p>
<h4 id="youredone">You're done!</h4>
<p>Now the new deployment should mount the volume. Usually it takes a bit to reattach the EBS/DO volume, so don't get discouraged if you need to wait a minute or two.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Running privileged containers as system services in Swarm mode]]></title><description><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p><strong>Update 11 Jun 2019:</strong> A patch by <a href="https://github.com/docker/swarmkit/issues/1030#issuecomment-501019288">Olli Janatuinen</a> that implements this feature has been merged and should be shipped in Docker 19.06 / 19.09. Thanks Olli!</p>
<p>This title might be misleading so let me clarify: when a node is in Swarm mode, services and stacks <a href="https://github.com/docker/swarmkit/issues/1030">don't support <code>privileged</code></a></p>]]></description><link>https://suda.pl/privileged-containers-in-swarm/</link><guid isPermaLink="false">5ad3587064a8670001cfcbb3</guid><category><![CDATA[docker]]></category><category><![CDATA[swarm]]></category><category><![CDATA[systemd]]></category><category><![CDATA[coreos]]></category><dc:creator><![CDATA[Wojtek Siudzinski]]></dc:creator><pubDate>Sun, 15 Apr 2018 16:14:55 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p><strong>Update 11 Jun 2019:</strong> A patch by <a href="https://github.com/docker/swarmkit/issues/1030#issuecomment-501019288">Olli Janatuinen</a> that implements this feature has been merged and should be shipped in Docker 19.06 / 19.09. Thanks Olli!</p>
<p>This title might be misleading so let me clarify: when a node is in Swarm mode, services and stacks <a href="https://github.com/docker/swarmkit/issues/1030">don't support <code>privileged</code> or <code>cap_add</code> options</a>. It might be a problem if you're running <a href="https://concourse-ci.org/">Concourse</a> or any kind of VPN, you might need this (like I did). This still works if the container was started with <code>docker run</code> but then it has to be manually started and supervised.</p>
<p>Thankfully <code>systemd</code> to the rescue! Following <a href="https://coreos.com/os/docs/latest/using-systemd-drop-in-units.html">CoreOS systemd pattern</a> of running system (not Docker Swarm) services in Docker, we can create following unit file:</p>
<pre><code class="language-ini">[Unit]
# Set it to your description
Description=My Docker Container service
# Make sure Docker and networking are set up before running this
After=network.target docker.socket
Requires=docker.socket

[Service]
# Equivalent of Docker's `restart_policy`
RestartSec=10
Restart=always
# Give `docker pull` some time
TimeoutStartSec=90
# Use instance name as the container name
Environment=&quot;CONTAINER_NAME=%i&quot;
# Set it to your image name
Environment=&quot;IMAGE_NAME=alpine:latest&quot;

# Remove any stale containers
ExecStartPre=-/usr/bin/docker rm -f $CONTAINER_NAME
# Pull the image
ExecStartPre=-/usr/bin/docker pull $IMAGE_NAME
# Start the container in the foreground
ExecStart=/usr/bin/docker run --rm --name ${CONTAINER_NAME} ${IMAGE_NAME}
# Stop command
ExecStop=/usr/bin/docker stop $CONTAINER_NAME

[Install]
WantedBy=multi-user.target
</code></pre>
<p>as <code>my-service@.service</code> (replace <code>my-service</code> with your service name) and save it in <code>/lib/systemd/system</code> on Ubuntu or <code>/var/systemd/system</code> on other systems.</p>
<p>Now you can create an instance of your service. The <code>@</code> in service name made it an <a href="https://www.freedesktop.org/software/systemd/man/systemd.unit.html">instantiated unit</a> allowing it to have multiple instances from one config file:</p>
<pre><code class="language-shell">$ sudo systemctl enable --now my-service@INSTANCE_NAME.service
</code></pre>
<p>Now running <code>docker ps</code> should show a container named <code>INSTANCE_NAME</code> with your service.</p>
<p>To check the status and logs of your service, you can use <code>systemctl status</code> and <code>journalctl</code>:</p>
<pre><code class="language-shell">$ sudo systemctl status my-service@INSTANCE_NAME.service
$ sudo journalctl --unit my-service@INSTANCE_NAME.service -r
</code></pre>
<p><strong>Note:</strong> I'm using <code>-r</code> flag for <code>journalctl</code> to see the latest line first. You can also use <code>-f</code> to follow the logs.</p>
<p>Great example of a container running as a <code>systemd</code> service is <a href="https://github.com/kylemanna/docker-openvpn/blob/master/docs/systemd.md">OpenVPN image from Kyle Manna</a>.</p>
<!--kg-card-end: markdown--><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Migrating from Docker Cloud to Docker Swarm with Portainer]]></title><description><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p>The announcement that <a href="http://success.docker.com/article/cloud-migration">Docker Cloud is shutting down</a> was sudden for everyone. It wasn't much of a surprise as it wasn't updated in a long time. But <a href="https://forums.docker.com/t/is-docker-cloud-being-discontinued/44181">lack of transparency/reponse from Docker</a> and the decision of giving <strong>only 60 days to migrate</strong> out of DC made <a href="https://www.reddit.com/r/docker/comments/85w2vd/docker_cloud_is_shutting_down/">a lot of</a></p>]]></description><link>https://suda.pl/migrating-from-docker-cloud-to-docker-swarm-with-portainer/</link><guid isPermaLink="false">5ad268f5363dbd000114c3bf</guid><category><![CDATA[docker]]></category><category><![CDATA[swarm]]></category><category><![CDATA[portainer]]></category><category><![CDATA[stackfile]]></category><category><![CDATA[docker-compose]]></category><category><![CDATA[docker cloud]]></category><dc:creator><![CDATA[Wojtek Siudzinski]]></dc:creator><pubDate>Sat, 14 Apr 2018 17:35:30 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p>The announcement that <a href="http://success.docker.com/article/cloud-migration">Docker Cloud is shutting down</a> was sudden for everyone. It wasn't much of a surprise as it wasn't updated in a long time. But <a href="https://forums.docker.com/t/is-docker-cloud-being-discontinued/44181">lack of transparency/reponse from Docker</a> and the decision of giving <strong>only 60 days to migrate</strong> out of DC made <a href="https://www.reddit.com/r/docker/comments/85w2vd/docker_cloud_is_shutting_down/">a lot of people angry/anxious</a>. Looking at alternatives there seem to be two viable ones in my eyes:</p>
<ul>
<li><a href="https://docs.docker.com/engine/swarm/">Docker Swarm</a> + a management UI like <a href="https://www.portainer.io/">Portainer</a></li>
<li><a href="https://kubernetes.io/">Kubernetes</a> hosted on <a href="https://cloud.google.com/kubernetes-engine/">GKE</a> / <a href="https://kubernetes.io/docs/getting-started-guides/azure/">ACS</a> / AWS with <a href="https://github.com/kubernetes/kops"><code>kops</code></a></li>
</ul>
<p>I know there are many other 3rd party tools like <a href="https://rancher.com/">Rancher</a>, <a href="https://app.cloud66.com/referral?code=GOjUTIQEcnHLIvB">Cloud 66</a> or <a href="https://kontena.io/">Kontena</a> but honestly I'm afraid they'll end up sharing the fate of Tutum/Docker Cloud.</p>
<p>Kubernetes is amazing powerhouse and deserves its own post but in here I'll focus on easier migration to Docker Swarm.</p>
<h3 id="firststep">First step</h3>
<p>Reach out to Docker and ask for extension. By default you get 60 days but <strong>they can extend it to 120 days if you ask</strong>. You might not need it but I'd recommend doing it just in case.</p>
<h3 id="whatwillchange">What will change?</h3>
<p>Docker Cloud agent installs very old Docker engine version <a href="https://docs.docker.com/release-notes/docker-engine/#1112-2016-05-31">1.11.2-cs5 released almost two years ago</a> (we should've known right...) so updating to modern version will come with many nice improvements like Swarm mode, support for secrets, routing mesh, rolling updates, <code>overlay2</code> storage driver and more (yeah, we missed a lot...).</p>
<p><strong>Warning:</strong> Upgrading Docker will cause a downtime until the migration is complete so either provision new machines and migrate to them or if you're ok with it, perform the upgrade while apps are in &quot;maintenance&quot;.</p>
<h3 id="beforeupgrading">Before upgrading</h3>
<p>If you're using data volumes you might need to perform some additional steps. Unfortunately volumes created automatically by Docker Cloud don't have human readable names, only a hash. Therefore it's hard to know to which service they belong. You can run this one-liner to peek into all running containers and output their ID and currently mounted volumes:</p>
<pre><code class="language-shell">$ docker ps -q | \
    while read p; do \
        docker inspect $p | sed -n -e '/&quot;Id&quot;/,/&quot;Created&quot;/ p'; \
        docker inspect $p | sed -n -e '/&quot;Mounts&quot;/,/&quot;Config&quot;/ p'; \
        echo -e &quot;\n\n\n&quot;; \
    done
</code></pre>
<p>You can copy this output somewhere as it will be needed later.</p>
<h3 id="upgradingdocker">Upgrading Docker</h3>
<p>First we need to remove the old Docker Cloud agent, following <a href="https://docs.docker.com/docker-cloud/infrastructure/byoh/#uninstall-the-docker-cloud-agent">the docs</a>:</p>
<pre><code class="language-shell">$ apt-get remove dockercloud-agent
</code></pre>
<p>This should keep all your images, volumes etc in <code>/home/docker</code> directory. Docker CE installation will keep everything in <code>/var/lib/docker</code> so the next step is to copy (you can move but it's nice to have a backup) them there:</p>
<pre><code class="language-shell">$ cp -R /home/docker /var/lib
</code></pre>
<p>Now to proceed with installation of latest docker following <a href="https://docs.docker.com/install/linux/docker-ce/ubuntu/">docs</a>:</p>
<pre><code class="language-shell">$ sudo apt-get update
$ sudo apt-get 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 \
   &quot;deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable&quot;
$ sudo apt-get update
$ sudo apt-get install docker-ce
</code></pre>
<p><strong>Note:</strong> you can use <code>edge</code> channel instead of <code>stable</code> but it's not recommended for production.</p>
<p>At this point you should have a running Docker CE with access to your past data, which can be verified with:</p>
<pre><code class="language-shell">$ sudo docker images
</code></pre>
<h3 id="startingaswarm">Starting a Swarm</h3>
<p>This step is optional but if you're working with more than one machine and/or you want to use Services and Stacks you need to create a Swarm. Again, following <a href="https://docs.docker.com/engine/swarm/swarm-tutorial/create-swarm/">the docs</a>:</p>
<pre><code class="language-shell">$ docker swarm init
</code></pre>
<p>If you have more nodes, it is recommended to at least three of them are managers (to maintain fault tolerance). You can make two more join the swarm using the manager token from the command above:</p>
<pre><code class="language-shell">$ docker swarm join --token MANAGER_TOKEN
</code></pre>
<p>On the worker nodes you can run:</p>
<pre><code class="language-shell">$ docker swarm join --token WORKER_TOKEN
</code></pre>
<h3 id="startingportainer">Starting Portainer</h3>
<p><a href="https://www.portainer.io/">Portainer</a> is a lightweight UI for Docker and is a great replacement for the Docker Cloud UI. You can try the demo here.</p>
<p>To start Portainer, you can <a href="https://portainer.readthedocs.io/en/latest/deployment.html#connect-to-a-swarm-cluster">create it as a service</a>. First create a volume to persist the config:</p>
<pre><code class="language-shell">$ docker volume create portainer-data
</code></pre>
<p>Then start the service:</p>
<pre><code class="language-shell">$ docker service create \
    --name portainer \
    --publish 9000:9000 \
    --constraint 'node.role == manager' \
    --mount type=bind,src=//var/run/docker.sock,dst=/var/run/docker.sock \
    --mount src=portainer-data,dst=/data \
    portainer/portainer \
    -H unix:///var/run/docker.sock
</code></pre>
<p>Now the web UI should be available on the port <code>9000</code> of any of your nodes <a href="https://docs.docker.com/engine/swarm/services/#publish-ports">thanks to the routing mesh</a>.</p>
<h3 id="migratingthestackfiles">Migrating the Stackfiles</h3>
<p>The Stackfiles are basically <a href="https://docs.docker.com/compose/compose-file/#compose-and-docker-compatibility-matrix">Docker Compose 1.0 files</a> with some additional options.</p>
<h4 id="removingunsupportedoptions">Removing unsupported options</h4>
<p>The additions are not supported by regular Docker and need to be removed fromt the Stackfile:</p>
<ul>
<li>autodestroy</li>
<li>autoredeploy</li>
<li>deployment_strategy</li>
<li>net</li>
<li>roles</li>
<li>sequential_deployment</li>
<li>tags</li>
<li>target_num_containers</li>
</ul>
<h5 id="optionsnotsupportedbydockerstackdeploycommand">Options not supported by <code>docker stack deploy</code> command</h5>
<p>Additionally following options are <a href="https://docs.docker.com/compose/compose-file/#not-supported-for-docker-stack-deploy">not supported</a> when deploying a stack:</p>
<ul>
<li>build</li>
<li>cap_add</li>
<li>cap_drop</li>
<li>cgroup_parent</li>
<li>container_name</li>
<li>depends_on</li>
<li>devices</li>
<li>tmpfs</li>
<li>external_links</li>
<li>links</li>
<li>network_mode</li>
<li>restart</li>
<li>security_opt</li>
<li>stop_signal</li>
<li>sysctls</li>
<li>userns_mode</li>
</ul>
<h4 id="upgradingdockercomposefileversion">Upgrading Docker Compose file version</h4>
<p>That one is easier one. The biggest difference between version 1.0 and the later ones is that services instead of being top-level objects, are now properties of <code>services</code> key and have a <code>version</code> key that specifies the version:</p>
<h5 id="10">1.0</h5>
<pre><code class="language-yaml">foo-service:
  image: nginx
</code></pre>
<h5 id="36">3.6</h5>
<pre><code class="language-yaml">version: '3.6'
services:
  foo-service:
    image: nginx
</code></pre>
<h4 id="usingvolumescreatedbydockercloud">Using volumes created by Docker Cloud</h4>
<p>To use existing volumes you need to know their ID. If you followed the <a href="#beforeupgrading">step above</a> you should have this information. Now you need to add it to the Compose file:</p>
<pre><code class="language-yaml">volume:
  YOUR_VOLUME_ID:
    external: true

services:
  foo-service:
    image: nginx
    volumes:
      - type: volume
        source: YOUR_VOLUME_ID
        target: DIRECTORY_WHERE_IT_WAS_MOUNTED
</code></pre>
<h3 id="epilogue">Epilogue</h3>
<p>Once the Stackfile is migrated you can upload it to Portainer and deploy the stack!</p>
<p>Repeat this for all your stacks and you're done!</p>
<p><img src="https://media.giphy.com/media/ORfj1id4J68qQ/giphy.gif" alt=""></p>
<p>P.S.: Bugsnag has a nice <a href="https://blog.bugsnag.com/container-orchestration-with-docker-swarm-mode/">Swarm lessons learned blogpost</a> that can help with running Swarm in production.</p>
<!--kg-card-end: markdown--><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Improving UI of a Chinese car head unit]]></title><description><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p>Last couple of years I had the pleasure (and sometimes lack thereof) to drive a variety of different cars mostly due to not owning a car and having to rent one when needed. Some of them were <a href="https://www.instagram.com/p/BQ3AMPNgH55">cute</a>, some were <a href="https://www.instagram.com/p/3GhmNBN-nY">my childhood dreams</a> and then there was <a href="https://www.youtube.com/watch?v=hxRHxL3wfB8">Tesla Model S</a></p>]]></description><link>https://suda.pl/improving-ui-of-a-chinese-car-head-unit/</link><guid isPermaLink="false">5ad268f5363dbd000114c3be</guid><category><![CDATA[android]]></category><category><![CDATA[nova launcher]]></category><category><![CDATA[gboard]]></category><category><![CDATA[automate]]></category><dc:creator><![CDATA[Wojtek Siudzinski]]></dc:creator><pubDate>Sun, 11 Mar 2018 19:22:58 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p>Last couple of years I had the pleasure (and sometimes lack thereof) to drive a variety of different cars mostly due to not owning a car and having to rent one when needed. Some of them were <a href="https://www.instagram.com/p/BQ3AMPNgH55">cute</a>, some were <a href="https://www.instagram.com/p/3GhmNBN-nY">my childhood dreams</a> and then there was <a href="https://www.youtube.com/watch?v=hxRHxL3wfB8">Tesla Model S</a>. One of the things I loved about this car (the list is long) was the centre console:</p>
<p><img src="https://scontent-mad1-1.cdninstagram.com/vp/c24404685a82c7c9fd90afb8e5166868/5B4E19AF/t51.2885-15/s640x640/sh0.08/e35/15338542_1839682629605804_5123230929917050880_n.jpg" alt=""></p>
<p>The fact that you can control <strong>everything</strong> from it (including AC, trunks, seats), the integrated GPS (so integrated it actually is showing your route on the main console next to the speedometer), ability to have the backing camera on while looking on a map just blew me away. It definitely felt like a good UI/UX made to be used in a car. It became my benchmark.</p>
<p>Some infotainments were just that (but with touch control), some tried mimicking Tesla (I'm talking to you Renault Megane) but they either felt extremely dated or like they were designed by car engineers.</p>
<p>Fast forward to this February, when I decided to get a car (went with 2006 Peugeot 206CC because I won't spend my whole salary on a car I'm going to use on weekends) as it came with a standard issue Clarion radio, I thought it's a chance to prototype a tailor suited car experience for me.</p>
<p>I decided to go with <a href="https://www.aliexpress.com/item/MAX-GPS-Navigation-System-Android-7-1-Car-DVD-Player-for-Peugeot-206-2000-2016-with/32851394936.html">Android 7.1 based head unit</a>. Everything worked as advertised except the UI was irritating my non diagnosed OCD:</p>
<p><img src="http://suda-dropshare.s3-eu-west-1.amazonaws.com/IMG_5377.jpg" alt=""></p>
<p>Fortunately this runs Android with means I can customise it to my will! Lets start with...</p>
<h2 id="thestatusbar">The status bar</h2>
<p>Both Home and Back buttons are present on the device so there's no need to have them around. As for the task switcher and options, I can live without it. This is not an easily customisable part of Android. Any app that does that basically intercepts any input and draws itself on top of the system one. Problem with this trick is that the system notification pulldown goes around it and hides the hacked status bar. I found that this can be solved using both:</p>
<h4 id="status"><a href="https://play.google.com/store/apps/details?id=com.james.status">Status</a></h4>
<p><a href="https://play.google.com/store/apps/details?id=com.james.status"><img src="https://lh3.googleusercontent.com/iqDsRaWtCl4aV5OzKb8FzKuO2p6ntSplrLCNc5K1beRZRsrbcRWmWFJrLSK-EO1obas=w128" alt=""></a></p>
<h4 id="materialnotificationshade"><a href="https://play.google.com/store/apps/details?id=com.treydev.mns">Material Notification Shade</a></h4>
<p><a href="https://play.google.com/store/apps/details?id=com.treydev.mns"><img src="https://lh3.googleusercontent.com/tFsyV0igeUQD1R3ignm86WyFMfy-3ED43Zb21J0kP_XrRq1XTTouZcubm52bnrlNXQ=w128" alt=""></a></p>
<p><strong>Note:</strong> In my version of Android, for some reason &quot;Accessibility&quot; option is hidden (which is needed to enable the services) but you can find it using search in settings.</p>
<h2 id="thehomescreen">The home screen</h2>
<p>I wanted to have a bit cleaner and more unified view of the home screen and after a bit of trial and error I went with:</p>
<h4 id="novalauncher"><a href="https://play.google.com/store/apps/details?id=com.teslacoilsw.launcher">Nova Launcher</a></h4>
<p><a href="https://play.google.com/store/apps/details?id=com.teslacoilsw.launcher"><img src="https://lh3.googleusercontent.com/0Nt6bbUFoUjSrTZkxbPvklq8szI-Ojk-tcE5V9Uq3RQ9c1f3AOQvKCOACU7nPPMmig=w128" alt=""></a></p>
<h4 id="forestlivewallpaper"><a href="https://play.google.com/store/apps/details?id=kaka.wallpaper.forest">Forest Live Wallpaper</a></h4>
<p><a href="https://play.google.com/store/apps/details?id=kaka.wallpaper.forest"><img src="https://lh5.ggpht.com/kBjZYAKbI1IVCP_-seWR0bPluKR4gzPqXo4GYxVD1UvMn88UQcYfYUrorYOzYn_o5A=w128" alt=""></a></p>
<h2 id="other">Other</h2>
<p>Other apps I found useful:</p>
<h4 id="gboard"><a href="https://play.google.com/store/apps/details?id=com.google.android.inputmethod.latin">Gboard</a></h4>
<p><a href="https://play.google.com/store/apps/details?id=com.google.android.inputmethod.latin"><img src="https://lh3.googleusercontent.com/X64En0aW6jkvDnd5kr16u-YuUsoJ1W2cBzJab3CQ5lObLeQ3T61DpB7AwIoZ7uqgCn4=w128" alt=""></a></p>
<p>Has a very nice dark there, perfect for in car use.</p>
<h4 id="automate"><a href="https://play.google.com/store/apps/details?id=com.bitspice.automate">Automate</a></h4>
<p><a href="https://play.google.com/store/apps/details?id=com.bitspice.automate"><img src="https://lh3.googleusercontent.com/q68a-4gWefMXQpdUgImlML2ZOaL2OHKROWCYxcnY7eXmlEADM01rUIWssXnlaYEYDA=w128" alt=""></a></p>
<p>A very nice Android Auto like app (it can also run as a launcher).</p>
<h2 id="finishedproduct">Finished product</h2>
<p><img src="http://suda-dropshare.s3-eu-west-1.amazonaws.com/home_screen.jpg" alt=""><br>
<img src="http://suda-dropshare.s3-eu-west-1.amazonaws.com/material_notification_shade.jpg" alt=""><br>
<img src="http://suda-dropshare.s3-eu-west-1.amazonaws.com/all_apps.jpg" alt=""></p>
<p>There's still a bit work to be done there but in general I'm quite happy with the result 😊</p>
<h3 id="sources">Sources</h3>
<p>If you want to replicate this setup, I collected all app settings <a href="https://github.com/suda/android-car-ui">in a GitHub repo</a>. You just need to copy and restore them on your device.</p>
<!--kg-card-end: markdown--><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Cheapest ($6.86) hosted InfluxDB, Chronograf and Kapacitor with Hyper.sh]]></title><description><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p>The <a href="https://www.influxdata.com/use-cases/introducing-the-tick-stack/">TICK stack</a> is a very powerful architecture for managing time-series data. It can be used for everything from monitoring server infrastructure to process <a href="https://www.influxdata.com/use-cases/iot-and-sensor-data/">IoT data</a>. Unfortunately hosted <a href="https://help.influxcloud.net/cloud_pricing/">InfluxCloud starts at $99 a month</a> which might be too much when considering it for experiments or prototypes.</p>
<p>Thankfully <a href="http://mkozak.pl/">Matt</a> pointed me</p>]]></description><link>https://suda.pl/cheapest-6-86-hosted-influxdb-chronograf-and-kapacitor-with-hyper-sh/</link><guid isPermaLink="false">5ad268f5363dbd000114c3bc</guid><category><![CDATA[docker]]></category><category><![CDATA[hypersh]]></category><category><![CDATA[tick]]></category><category><![CDATA[influxdb]]></category><category><![CDATA[kapacitor]]></category><category><![CDATA[chronograf]]></category><category><![CDATA[telegraf]]></category><dc:creator><![CDATA[Wojtek Siudzinski]]></dc:creator><pubDate>Sat, 12 Nov 2016 22:06:09 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p>The <a href="https://www.influxdata.com/use-cases/introducing-the-tick-stack/">TICK stack</a> is a very powerful architecture for managing time-series data. It can be used for everything from monitoring server infrastructure to process <a href="https://www.influxdata.com/use-cases/iot-and-sensor-data/">IoT data</a>. Unfortunately hosted <a href="https://help.influxcloud.net/cloud_pricing/">InfluxCloud starts at $99 a month</a> which might be too much when considering it for experiments or prototypes.</p>
<p>Thankfully <a href="http://mkozak.pl/">Matt</a> pointed me to interesting Docker hosting: <a href="https://console.hyper.sh/register/invite/zMaqodJg30I7aY7JmdBqCQOnWlwprOiG">Hyper.sh</a>. One of the reasons I like Hyper is quicker setup and going with &quot;Heroku for Docker&quot; approach making it perfect for proof on concepts or small deployments. It does support <a href="https://docs.hyper.sh/Reference/compose_file_ref.html">Docker Compose file format</a> so basing on <a href="https://github.com/influxdata/TICK-docker/blob/master/1.0/docker-compose.yml">InfluxData Compose file</a> I created one which works on Hyper.sh:</p>
<h3 id="followinstructionsinhypertickrepository"><a href="https://github.com/suda/hyper-tick#readme">👉🏻 Follow instructions in <code>hyper-tick</code> repository</a></h3>
<h2 id="whatsthecostofthis">What's the cost of this?</h2>
<p>Here's the breakdown based on <a href="https://docs.hyper.sh/FAQ/pricing.html">Hyper.sh pricing</a>:</p>
<ul>
<li>$1 - FIP (&quot;Floating&quot; IP, basically a public IP)</li>
<li>2x $1.55 - two S2 containers (<code>haproxy</code> and <code>influxdb</code> crash with just 64MB of ram)</li>
<li>2x $1.03 - two S1 containers for <code>chronograf</code> and <code>kapacitor</code></li>
<li>4x $0.1/GB - four images</li>
<li>3x $0.1/GB - three volumes attached to <code>influxdb</code>, <code>chronograf</code> and <code>kapacitor</code> (this might grow with amount of your data stored in InfluxDB)</li>
</ul>
<p>Making it total of <strong>$6.86 per month</strong>. Just low enough to allow everyone start their adventure with TICK stack.</p>
<h2 id="whatcanidowiththis">What can I do with this?</h2>
<p>If you're not familiar with TICK stack, here's list of possible applications from <a href="https://www.influxdata.com/use-cases/time-series-use-cases/">InfluxData use cases</a>:</p>
<ul>
<li>Custom DevOps Monitoring</li>
<li>Real Time Analytics</li>
<li>IoT and Sensor Data</li>
<li>Cloud &amp; OpenStack</li>
<li>Anomaly Detection</li>
<li>Messaging</li>
<li>Personalization</li>
<li>Equities Trading</li>
<li>Municipal Infrastructure Management</li>
<li>GPS Services</li>
<li>Quantum Physics Research</li>
<li>Point of Sale Systems</li>
<li>Manufacturing &amp; Home Automation</li>
<li>Transportation &amp; Material Logistics</li>
</ul>
<p>What I'm currently using it for is server monitoring and alerting:</p>
<p><img src="https://suda-dropshare.s3-eu-west-1.amazonaws.com/Screen-Shot-2016-11-12-at-21.41.46.png" alt="Chronograf showing CPU, RAM, HDD and Docker stats"></p>
<p>To report system stats you need to install <a href="https://www.influxdata.com/downloads/">Telegraf</a> on your server and point it to your InfluxDB. Then it's just a matter of creating visualisations in <a href="https://www.influxdata.com/time-series-platform/chronograf/">Chronograf</a> and alerts in <a href="https://www.influxdata.com/time-series-platform/kapacitor/">Kapacitor</a>.</p>
<!--kg-card-end: markdown--><!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Making projects more accessible for the new contributors]]></title><description><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p>What all big, successful projects like Django, Ruby on Rails, Node.js have in common? None of them has a single maintainer. They may have started as small, one person side projects, but what made them grow were outside contributions. There are a lot of great ideas in projects/repositories</p>]]></description><link>https://suda.pl/making-projects-more-accessible-for-the-new-contributors/</link><guid isPermaLink="false">5ad268f5363dbd000114c3bb</guid><category><![CDATA[community]]></category><category><![CDATA[contributions welcome]]></category><category><![CDATA[open source]]></category><dc:creator><![CDATA[Wojtek Siudzinski]]></dc:creator><pubDate>Wed, 06 Apr 2016 22:20:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><!--kg-card-begin: markdown--><p>What all big, successful projects like Django, Ruby on Rails, Node.js have in common? None of them has a single maintainer. They may have started as small, one person side projects, but what made them grow were outside contributions. There are a lot of great ideas in projects/repositories that where open sourced to share knowledge and help others. Unfortunately very often they get forgotten and abandoned. Not just by maintainers but also users who had issues with usage or were unable to fix something.</p>
<p>Having this same problem, I did a survey trying to find common issues:</p>
<blockquote class="twitter-tweet" data-lang="en"><p lang="en" dir="ltr">I would ❤️ some input from OSS contributors about what makes a project/repo friendly for new contributors: <a href="https://t.co/S4kFp93Jvx">https://t.co/S4kFp93Jvx</a></p>&mdash; Wojtek Siudzinski (@suda) <a href="https://twitter.com/suda/status/709424509930053632">March 14, 2016</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
<p>I got a lot of interesting feedback with great initiatives like <a href="https://medium.com/@kentcdodds/first-timers-only-78281ea47455#.wgpadoo32">First Timers Only</a>, <a href="http://yourfirstpr.github.io/">Your First PR</a> or <a href="http://up-for-grabs.net/#/">Up For Grabs</a>. It's amazing that there are so many great people improving our community! With all the data collected I decided to help a bit too and compile it into a small checklist for project maintainers and call it:</p>
<h1 id="contributionswelcome"><a href="http://contributionswelcome.org/">Contributions Welcome</a></h1>
<p>Hopefully this will be useful for any project maintainer out there and I will do my best to keep my repos contribution friendly too!</p>
<p><a href="http://contributionswelcome.org/">Contributions Welcome</a> welcomes contributions as well! Feel free to share your thoughts and comments on <a href="https://github.com/suda/contributions-welcome/issues">GitHub Issues page</a>.</p>
<!--kg-card-end: markdown--><!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>