<?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: Sergio Marin</title>
    <description>The latest articles on Forem by Sergio Marin (@highercomve).</description>
    <link>https://forem.com/highercomve</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%2F417039%2F96a9ccce-93e4-4602-82ca-b6c318f471fc.jpeg</url>
      <title>Forem: Sergio Marin</title>
      <link>https://forem.com/highercomve</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/highercomve"/>
    <language>en</language>
    <item>
      <title>Reducing docker images size</title>
      <dc:creator>Sergio Marin</dc:creator>
      <pubDate>Fri, 21 Oct 2022 18:13:14 +0000</pubDate>
      <link>https://forem.com/highercomve/optimize-your-docker-containers-20gg</link>
      <guid>https://forem.com/highercomve/optimize-your-docker-containers-20gg</guid>
      <description>&lt;p&gt;You might have been building containers for a long time, and in all your previous builds, the security and/or the size of your containers wasn't the priority. But if you got here now you are giving priority to those topics.&lt;/p&gt;

&lt;p&gt;For now we can focus first on the container size, and for this lets talk about a couple of options here.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build upon a minimal base image.&lt;/li&gt;
&lt;li&gt;Use multi-stage builds.&lt;/li&gt;
&lt;li&gt;Create containers with statically linked applications.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Build upon a minimal base image
&lt;/h3&gt;

&lt;p&gt;Instead of using a big image like ubuntu, debian, etc. Which could be a huge image for what is needed, we can use some alternatives that will allow us to start from a much smaller base.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;debian latest d91720f514f7 124MB
ubuntu latest 216c552ea5ba 77.8MB

alpine latest 9c6f07244728 5.54MB
gcr.io/distroless/static-debian11 latest 561bdfb51245 2.35MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The alpine and google distroless container are way smaller that ubuntu or debian.&lt;/p&gt;

&lt;p&gt;In the case of google distroless container the only problem will be the package management for that container, because it wont have any package manager, another thing “missing” will be a shell. But they have a good number of base images for several runtime.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gcr.io/distroless/static-debian11
gcr.io/distroless/base-debian11
gcr.io/distroless/cc-debian11
gcr.io/distroless/python3-debian11
gcr.io/distroless/java-base-debian11
gcr.io/distroless/java11-debian11
gcr.io/distroless/java17-debian11
gcr.io/distroless/nodejs-debian11
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alpine on the other hand is a distro container, therefore it has a package manager and a shell, but it is small enough to be use has base container.&lt;/p&gt;

&lt;p&gt;Lets start with an easy go application, you can see the source code of the application &lt;a href="https://github.com/highercomve/better-container-example"&gt;here&lt;/a&gt;. The container will expose a web application by default in the port 5000 with a general information of the system.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Mjk8zewX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2Aqj4qPO7l7DVa22cekRCcHg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Mjk8zewX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2Aqj4qPO7l7DVa22cekRCcHg.png" alt="" width="880" height="584"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we start with the basic way of building this container, will be something like this.&lt;br&gt;
&lt;/p&gt;

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

WORKDIR /app

COPY . /app/

RUN go mod download; \
    CGO_ENABLED=0 go build -ldflags="-s -w" -o bce -v .

EXPOSE 5000

ENTRYPOINT ["/app/bce"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And after this we can build the container with the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker build -t highercomve/bce .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this we will end with a whooping 1.02GB size for the container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;highercomve/bce latest 614d597dbfb5 4 seconds ago 1.02GB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That doesn’t sound that good. Lets now change the the base container from &lt;strong&gt;golang&lt;/strong&gt; to &lt;strong&gt;golang:alpine.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

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

WORKDIR /app

COPY . /app/

RUN go mod download; \
    CGO_ENABLED=0 go build -ldflags="-s -w" -o bce -v .

EXPOSE 5000

ENTRYPOINT ["/app/bce"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And with only this change we now are in only 375MB&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;highercomve/bce latest 1fb5114c906e About a minute ago 375MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same container, same application a lot less space.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use multi-stage builds
&lt;/h3&gt;

&lt;p&gt;Now the second optimization we can do is use a multi-stage build. For this we need to mark in the FROM command the name the stage will have, an then we use another base as the final runtime of the container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM golang:alpine as builder # FIRST STAGE

WORKDIR /app

COPY . /app/

RUN go mod download; \
    CGO_ENABLED=0 go build -ldflags="-s -w" -o bce -v .

FROM gcr.io/distroless/static-debian11

WORKDIR /app

COPY --from=builder /app/bce /app/bce # IMPORT FROM THAT STAGE
COPY static /app/static/
COPY templates /app/templates/

EXPOSE 5000

ENTRYPOINT ["/app/bce"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now after this change we can see the difference it will be huge, because the resulting image will only be 8.73MB in size.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;highercomve/bce latest e68c3ebb73e8 About a minute ago 8.73MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create containers with statically linked applications
&lt;/h3&gt;

&lt;p&gt;Another thing you could do is build the application with the libraries linked and use the scratch base container.&lt;br&gt;
&lt;/p&gt;

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

WORKDIR /app

COPY . /app/

RUN go mod download; \
    CGO_ENABLED=0 go build -ldflags="-s -w -extldflags=-static" -o bce -v .

FROM scratch

WORKDIR /app
COPY --from=builder /app/bce /app/bce
COPY static /app/static/
COPY templates /app/templates/

EXPOSE 5000
ENTRYPOINT ["/app/bce"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the change we will reduce another 2MB in size&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;highercomve/bce latest f32c277bf5a6 7 seconds ago 6.39MB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now as a bonus i will add how to use this techniques to build a multi arch build container. Maybe you have the same application but you need to run it in arm or riscv architecture. For this we can use the buildx plugin from docker &lt;a href="https://github.com/docker/buildx"&gt;https://github.com/docker/buildx&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And for the same container i will use two arguments that are going to be injected by buildx.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ARG TARGETPLATFORM
ARG BUILDPLATFORM
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And will allow us to take decisions about how to configure the environment variables for golang. I will use this build.sh script to map the target platform variable injected by buildx to the golang environment variables for building.&lt;br&gt;
&lt;/p&gt;

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

echo "Building for $TARGETPLATFORM" 
export CGO_ENABLED=0

case "$TARGETPLATFORM" in
 "linux/arm/v6"*)
  export GOOS=linux GOARCH=arm GOARM=6
  ;;
 "linux/arm/v7"*)
  export GOOS=linux GOARCH=arm GOARM=7
  ;;
 "linux/arm64"*)
  export GOOS=linux GOARCH=arm64 GOARM=7
  ;;
 "linux/386"*)
  export GOOS=linux GOARCH=386
  ;;
 "linux/amd64"*)
  export GOOS=linux GOARCH=amd64
  ;;
 "linux/mips"*)
  export GOOS=linux GOARCH=mips
  ;;
 "linux/mipsle"*)
  export GOOS=linux GOARCH=mipsle
  ;;
 "linux/mips64"*)
  export GOOS=linux GOARCH=mips64
  ;;
 "linux/mips64le"*)
  export GOOS=linux GOARCH=mips64le
  ;;
 "linux/riscv64"*)
  export GOOS=linux GOARCH=riscv64
  ;;
 *)
  echo "Unknown machine type: $machine"
  echo "Building using host architecture"
esac

go mod download
go build -ldflags="-s -w -extldflags=-static" -o bce -v .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need to be sure of one thing, the compilation process needs to run in the same architecture as the host building the container, if not we may lose performance trough emulation. For this we can use the &lt;strong&gt;BUILDPLATFORM&lt;/strong&gt; argument in the FROM definition on the dockerfile.&lt;/p&gt;

&lt;p&gt;Something like this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM --platform=$BUILDPLATFORM golang:alpine as builder

ARG TARGETPLATFORM
ARG BUILDPLATFORM

WORKDIR /app

COPY . /app/

RUN /app/build.sh

FROM scratch

WORKDIR /app

COPY --from=builder /app/bce /app/bce
COPY static /app/static/
COPY templates /app/templates/

EXPOSE 5000

ENTRYPOINT ["/app/bce"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this new Dockerfile we can build the container using the buildx plugin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker buildx build --platform linux/arm64 -t highercomve/bce --load .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In here we introduce 3 new arguments for the build command:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;platform: in here we can defined a list separated by coma of several architectures&lt;/li&gt;
&lt;li&gt;load: this argument configure docker buildx to load the image to our local docker environment after the build process is done.&lt;/li&gt;
&lt;li&gt;push: this will push to docker hub all the container images after the building process is done.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can build several platform in one command and push to docker hub.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker buildx build --platform linux/arm64,linux/amd64,linux/arm --push -t highercomve/bce .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--M7voW_D7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2Ahp2XzSxl57n96YwmtT1KFQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--M7voW_D7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2Ahp2XzSxl57n96YwmtT1KFQ.png" alt="" width="880" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Docker hub will support one images with multiple architectures and when some device pull with one of those architecture will get the correct one without any problem.&lt;/p&gt;

&lt;p&gt;If we want to test the container running with one architecture different for the one of our system we will need to load the qemu binaries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And after that run the container with the platform we like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -it -p 5000:5000 --platform linux/arm64 highercomve/bce
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example the output of the web page should be something like this&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ujT_5NJD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AAJ0_6-8QjYTY0AzXAnnp6g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ujT_5NJD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/1%2AAJ0_6-8QjYTY0AzXAnnp6g.png" alt="" width="880" height="584"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I hope this could be useful for you and help you to maintain more optimized containers&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;May the force be with you&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>dockerimage</category>
      <category>dockerfiles</category>
      <category>docker</category>
    </item>
    <item>
      <title>Running Containerized GUIs on LXC with Pantavisor Linux</title>
      <dc:creator>Sergio Marin</dc:creator>
      <pubDate>Sun, 12 Sep 2021 23:26:43 +0000</pubDate>
      <link>https://forem.com/highercomve/running-containerized-guis-on-lxc-with-pantavisor-linux-48ma</link>
      <guid>https://forem.com/highercomve/running-containerized-guis-on-lxc-with-pantavisor-linux-48ma</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2oJNua_U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AeD2CGJYG80mYacl3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2oJNua_U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AeD2CGJYG80mYacl3.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pantavisor Linux is an embedded framework for building, maintaining, and managing LXC containers. It presents an infinite number of possibilities for running applications at the smart device edge.&lt;/p&gt;

&lt;p&gt;In this use case, we’ll describe how to run containerized GUI applications that communicate via a socket to a containerized version of &lt;a href="https://wayland.freedesktop.org/"&gt;Wayland Composer&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Use Case
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--P99T-Cjb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2A2eWgKJNhnQBQRwK2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--P99T-Cjb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2A2eWgKJNhnQBQRwK2.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We will run two different applications for this example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;qt&lt;/strong&gt; : An opensource application written in &lt;a href="https://www.qt.io/"&gt;QT&lt;/a&gt;. You can see the source code for the application &lt;a href="https://github.com/onyazuka/Simple-system-monitor"&gt;here&lt;/a&gt;. The built container for it is here: &lt;a href="https://hub.docker.com/r/highercomve/qt-system-monitor"&gt;https://hub.docker.com/r/highercomve/qt-system-monitor&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;flutter-example&lt;/strong&gt; : This is another application built with &lt;a href="https://flutter.dev/"&gt;Flutter&lt;/a&gt;. Flutter is a framework developed by Google to build iOS, Android, and Desktop apps written in &lt;a href="https://dart.dev/multiplatform-apps"&gt;Dart&lt;/a&gt;. We will use the default &lt;a href="https://flutter.dev/docs/get-started/test-drive?tab=vscode#create-app"&gt;example Flutter app from Flutter&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both &lt;strong&gt;qt&lt;/strong&gt; and &lt;strong&gt;flutter-example&lt;/strong&gt; are built with Ubuntu containers that run in another Ubuntu container.&lt;/p&gt;

&lt;p&gt;You can see the source code for the application containers here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Flutter-example&lt;/strong&gt; : &lt;a href="https://gitlab.com/highercomve/flutter-weston-example"&gt;https://gitlab.com/highercomve/flutter-weston-example&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;qt&lt;/strong&gt; : &lt;a href="https://gitlab.com/highercomve/qt-simple-system-monitor"&gt;https://gitlab.com/highercomve/qt-simple-system-monitor&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the Wayland protocol and compositor, we selected &lt;a href="https://gitlab.freedesktop.org/wayland/weston"&gt;weston&lt;/a&gt;. Weston can be described as the reference implementation of a Wayland compositor. It is also a very useful environment in and of itself.&lt;/p&gt;

&lt;p&gt;With this, we now have the three base containers that will do the work of running the application and then displaying it on the monitor.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before You Begin
&lt;/h3&gt;

&lt;p&gt;You need a Raspberry PI 4, a display, an HDMI cable, and a mouse and keyboard connected to the Raspberry PI. By default, weston does not start without a connected keyboard and mouse.&lt;/p&gt;

&lt;p&gt;You can download a Pantavisor image and claim it at &lt;a href="https://hub.pantacor.com"&gt;hub.pantacor.com.&lt;/a&gt; If you don’t have an account on &lt;a href="http://hub.pantacor.com"&gt;hub.pantacor.com&lt;/a&gt;, create one and then claim the device by following this &lt;a href="https://docs.pantahub.com/get-started/"&gt;Getting started guide&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  How this works
&lt;/h3&gt;

&lt;p&gt;Because this demo is fairly straightforward, and also to make things simpler, you can clone and deploy my reference device directly to your device with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pvr clone https://pvr.pantahub.com/highercomve/gui_rpi64
cd gui_rpi64
pvr post -m "Add weston, flutter example and qt example" YOUR_DEVICE_CLONE_URL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those commands will push all the parts needed to your device so that you can see everything running. Inside the reference device is the following folder structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bsp&lt;/li&gt;
&lt;li&gt;awconnect&lt;/li&gt;
&lt;li&gt;pv-avahi&lt;/li&gt;
&lt;li&gt;weston&lt;/li&gt;
&lt;li&gt;flutter-example&lt;/li&gt;
&lt;li&gt;qt&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With all those containers we are going to build a communication system for the Wayland components that will work in this way:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VlaXrJtl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AwazsBxRSrwnZm4pn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VlaXrJtl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AwazsBxRSrwnZm4pn.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can read more about how Wayland works in their documentation about the architecture. But let’s dig into how this container works from the pantavisor perspective and how every container is built and how it works.&lt;/p&gt;

&lt;h3&gt;
  
  
  BSP
&lt;/h3&gt;

&lt;p&gt;The bsp folder contains the Board Support Package. It is also where Pantavisor runs.&lt;/p&gt;

&lt;h3&gt;
  
  
  awconnect
&lt;/h3&gt;

&lt;p&gt;This is the main container and platform for the system. It is very similar to the base OS for all running containers. The awconnect container is also the central point in the example app architecture with weston. Weston will use any sockets configured inside the awconnect container. This container will then pass the info down to the kernel, etc.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sharing sockets in the awconnect container
&lt;/h3&gt;

&lt;p&gt;To share sockets running in the main container, you must configure the storage parameters inside the awconnect src.json file.&lt;/p&gt;

&lt;p&gt;First, mount a permanent volume inside the /var/run/dbus folder of awconnect. This allows you to read and write from any container that wants to send its data to the monitor.&lt;/p&gt;

&lt;p&gt;To do that the awconnect src.json should look as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "#spec": "service-manifest-src@1",
  "args": {},
  "config": {},
  "docker_digest": "registry.gitlab.com/pantacor/pv-platforms/wifi-connect@sha256:5c889720c6243408049b7a2f9ec75b9f4e376f6cbc28e39e091420a1e19df2aa",
  "docker_name": "registry.gitlab.com/pantacor/pv-platforms/wifi-connect",
  "docker_source": "remote,local",
  "docker_tag": "arm32v5",
  "persistence": {
    "/var/run/dbus": "boot"
  },
  "template": "builtin-lxc-docker"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How the Weston container is built
&lt;/h3&gt;

&lt;p&gt;The weston container is built with alpine and also includes all of the packages to make the Wayland Compositor work with weston.&lt;/p&gt;

&lt;p&gt;You can see the source of this container &lt;a href="https://gitlab.com/pantacor/pv-platforms/alpine-weston"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The Dockerfile generates the container. It will look exactly like any other normal Dockerfile, since there is nothing different that needs to be done to make a container run in Pantavisor.&lt;/p&gt;

&lt;p&gt;However, the most important part is not the container itself, but rather the Pantavisor configuration inside the src.json file. It is here that we need to be able to read and write to the socket folder kept inside the awconnect container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "#spec": "service-manifest-src@1",
  "args": {
    "PV_LXC_EXTRA_CONF": "lxc.mount.entry = /volumes/awconnect/docker--var-run-dbus run/dbus none bind,rw,create=dir 0 0\n",
    "PV_RUNLEVEL": "platform"
  },
  "config": {},
  "docker_digest": "sha256:116cc7d42d3f7e513fad55688b618e61dcdae2dd37f8164a86eea80af00c03d9",
  "docker_name": "weston",
  "docker_source": "remote,local",
  "docker_tag": "latest",
  "persistence": {},
  "template": "builtin-lxc-docker"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All the docker_* parts of this file are generated using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pvr app add --from=registry.gitlab.com/pantacor/pv-platforms/alpine-weston:arm64v8 weston
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Adding Docker apps to Pantavisor
&lt;/h3&gt;

&lt;p&gt;You can use the pvr app add command to add more Docker applications, and to basically convert any Docker container into a Pantavisor container (just a normal LXC container but with our tooling to make it easy to manage). See: &lt;a href="https://docs.pantahub.com/pvr-docker-apps-experience/"&gt;Adding Apps from Docker&lt;/a&gt; for more information.&lt;/p&gt;

&lt;h3&gt;
  
  
  Communicating with the awconnect
&lt;/h3&gt;

&lt;p&gt;To enable the weston container to talk to the awconnect system, the following two arguments were added:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.- PV_LXC_EXTRA_CONF&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This configuration allows the pvr commands to extend the default LXC generated configuration. It is necessary to add extra LXC configuration in this way because when pvr updates or installs the container it also needs to generate the LXC configuration file.&lt;/p&gt;

&lt;p&gt;In here we configure the lXC container to mount the same volume of the /var/run/dbus on awconnect inside a /run/dbus folder with read/write permissions.&lt;/p&gt;

&lt;p&gt;The route of the awconnect volume is defined in the run.json file for that container. We will mount it using the LXC configuration parameter lxc.mount.entry&lt;/p&gt;

&lt;p&gt;You can read more about the LXC configuration in the &lt;a href="https://linuxcontainers.org/lxc/manpages/man5/lxc.container.conf.5.html"&gt;documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The end result of that configuration will be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lxc.mount.entry = /volumes/awconnect/docker--var-run-dbus run/dbus none bind,rw,create=dir 0 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2.- PV_RUNLEVEL&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The PV_RUNLEVEL argument configures the level of priority used to run the container. By default, all applications run in RUNLEVEL: app and all apps in that run level will wait for the platforms to start.&lt;/p&gt;

&lt;p&gt;With this configuration, the container should run correctly and it will act as the wayland composer.&lt;/p&gt;

&lt;h3&gt;
  
  
  flutter-example and qt
&lt;/h3&gt;

&lt;p&gt;And lastly, we have the containers that will run the GUI applications. As I mentioned before, they are built with this source code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;flutter-example&lt;/strong&gt; : &lt;a href="https://gitlab.com/highercomve/flutter-weston-example"&gt;https://gitlab.com/highercomve/flutter-weston-example&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;qt&lt;/strong&gt; : &lt;a href="https://gitlab.com/highercomve/qt-simple-system-monitor"&gt;https://gitlab.com/highercomve/qt-simple-system-monitor&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the configuration for both of them will be similar to the weston platform where we are going to modify the args value of the src.json in order to mount the awconnect volume with the sockets and configure both containers to run on RUNLEVEL platform.&lt;/p&gt;

&lt;h3&gt;
  
  
  Display the results
&lt;/h3&gt;

&lt;p&gt;After you’ve posted everything to your device and the device consumed the revision, the Raspberry Pi will restart. You will then see the System monitor application written on QT and the Flutter example of a counter clicker running on the Raspberry PI display.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2oJNua_U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AeD2CGJYG80mYacl3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2oJNua_U--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AeD2CGJYG80mYacl3.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Is important to know that you can install to a container any available application from your favorite Linux distribution (example: Ubuntu) with support for wayland. That means you will install more packages than it needs, but it is possible.&lt;/p&gt;

&lt;p&gt;I recommend that you look insides of the flutter-example and the QT system monitor containers to see the dependencies needed for the applications to run.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Thoughts
&lt;/h3&gt;

&lt;p&gt;If you have any questions or you want to know more about &lt;a href="https://www.pantavisor.io"&gt;Pantavisor&lt;/a&gt; and &lt;a href="https://hub.pantacor.com"&gt;hub.pantacor.com&lt;/a&gt; you can reach out to us on &lt;a href="https://twitter.com/pantahub"&gt;@pantahub&lt;/a&gt; on Twitter or join our &lt;a href="https://pantavisor.slack.com"&gt;Pantavisor Slack channel&lt;/a&gt;. We’d be happy to help.&lt;/p&gt;

</description>
      <category>lxc</category>
      <category>containers</category>
      <category>gui</category>
    </item>
    <item>
      <title>How to do Port Forwarding on LXC with Awall</title>
      <dc:creator>Sergio Marin</dc:creator>
      <pubDate>Mon, 23 Aug 2021 22:23:17 +0000</pubDate>
      <link>https://forem.com/highercomve/how-to-do-port-forwarding-on-lxc-with-awall-i5o</link>
      <guid>https://forem.com/highercomve/how-to-do-port-forwarding-on-lxc-with-awall-i5o</guid>
      <description>&lt;h3&gt;
  
  
  Intro
&lt;/h3&gt;

&lt;p&gt;We are going to be running our LXC containers with &lt;a href="https://www.pantavisor.io"&gt;pantavisor.io&lt;/a&gt; a Linux system to run containers on embedded devices built by &lt;a href="https://www.pantacor.com"&gt;Pantacor&lt;/a&gt;, in this way we will reduce the hassle of managing LXC containers, and we are going to run everything with a Raspberry PI 4.&lt;/p&gt;

&lt;p&gt;Pantavisor uses LXC to run containers on embedded computers. By default, all containers share the host’s network namespace and as a result, the LXC configuration for the network will be:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;lxc.net.[0].type = none&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://linuxcontainers.org/lxc/manpages/man5/lxc.container.conf.5.html#lbAO"&gt;LXC — Manpages — lxc.container.conf.5&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This means that if you have more than one container using the same network &lt;strong&gt;PORT,&lt;/strong&gt; the container initialization will fail. A way to solve that problem is to create the containers with a virtual ethernet type of network and then do a port forwarding from the host’s network to the container IP.&lt;/p&gt;

&lt;p&gt;In Docker, port forwarding is a straightforward configuration when you run the container. But for LXC containers it is not that straightforward. In this post, we will describe how to forward ports inside the &lt;em&gt;pantavisor.&lt;/em&gt; With Pantavisor tools you will create the LXC configuration and then use &lt;em&gt;awall&lt;/em&gt; to build the container IP tables that will manage the forwarding.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring Raspberry Pi as a DNS server for Adguard
&lt;/h3&gt;

&lt;p&gt;In this use case, you will use a Raspberry PI as a DNS server with the &lt;a href="https://hub.docker.com/r/adguard/adguardhome"&gt;adguard&lt;/a&gt; app that will filter any ads directly from DNS.&lt;/p&gt;

&lt;p&gt;First, let’s observe the Adguard ports on Docker with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run --name adguardhome \
    --restart unless-stopped \
    -v /my/own/workdir:/opt/adguardhome/work \
    -v /my/own/confdir:/opt/adguardhome/conf \
    -p 53:53/tcp -p 53:53/udp \
    -p 67:67/udp -p 68:68/udp \
    -p 3000:3000/tcp \
    -p 853:853/tcp \
    -p 784:784/udp -p 853:853/udp -p 8853:8853/udp \
    -p 5443:5443/tcp -p 5443:5443/udp \
    -d adguard/adguardhome
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ports to map are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;53 tcp/udp&lt;/strong&gt; : Plain DNS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;67/udp, 68 tcp/udp&lt;/strong&gt; : Add if you intend to use AdGuard Home as a DHCP server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3000/tcp&lt;/strong&gt; : Add if you want to use AdGuard Home’s admin panel and also run AdGuard Home as an HTTPS/DNS-over-HTTPS server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;853/tcp&lt;/strong&gt; : Add if you plan to run AdGuard Home as a DNS-over-TLS server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;784/udp , 853 udp, 8853/udp&lt;/strong&gt; : Add if you are going to run AdGuard Home as a DNS-over-QUIC server. You may only leave one or two of these.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5443/tcp, 5443/udp:&lt;/strong&gt; Add if you are going to run AdGuard Home as a DNSCrypt server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the Pantavisor schema, the &lt;a href="https://gitlab.com/pantacor/pv-platforms/wifi-connect"&gt;awconnect&lt;/a&gt; container does all of the network management for the default image for Pantavisor on Raspberry PI. This is what allows you to connect the Raspberry Pi to the internet via wifi or to share your wired internet through a wifi hotspot.&lt;/p&gt;

&lt;p&gt;It is this container that provides DNS and DHCP. In addition to this, you may have another container using port 3000.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before You Begin
&lt;/h3&gt;

&lt;p&gt;In order to work on this, we need to have a Pantavisor-enabled Raspberry PI 3+ or a Raspberry PI 4. You will also need to install the following on your development computer:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;fakeroot&lt;/li&gt;
&lt;li&gt;squashfs&lt;/li&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;li&gt;pvr cli&lt;/li&gt;
&lt;li&gt;Sign up for an account on &lt;a href="https://hub.pantacor.com/"&gt;hub.pantacor.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can install &lt;strong&gt;fakeroot&lt;/strong&gt; and &lt;strong&gt;squashfs&lt;/strong&gt; from your distro or with your OS package manager: apt, yum, apk, homebrew, etc.&lt;/p&gt;

&lt;p&gt;For example Ubuntu/Debian based distros install these utilities with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt install fakeroot squashfs-tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To install Docker for your particular OS, refer to the Docker &lt;a href="https://docs.docker.com/engine/install/"&gt;installation documentation&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Last but not least, you will also need to install the PVR cli to manage &lt;a href="https://www.pantavisor.io/"&gt;pantavisor&lt;/a&gt; devices inside &lt;a href="https://hub.pantacor.com/"&gt;hub.pantacor.com&lt;/a&gt;. You can find more about how to install PVR in Pantacor &lt;a href="https://docs.pantahub.com/install-pvr/"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Because we will send new revisions and configurations to the device remotely from your development computer, the device needs to connect to the &lt;a href="https://hub.pantacor.com/"&gt;hub.pantacor.com&lt;/a&gt; cloud service. You will need to create an account there.&lt;/p&gt;

&lt;p&gt;Now that you have everything installed, we can move forward to solve our network isolation and port forwarding problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  #1 Installing Pantavisor and claiming the device
&lt;/h3&gt;

&lt;p&gt;A claiming process may enable an end-user to claim a device and proof ownership thereof. Such a process is initially triggered via a claim message that could be done in several ways.&lt;/p&gt;

&lt;p&gt;After you’ve installed the Pantavisor image or if you installed Pantavisor already, you need to claim your device. There are several ways to claim your device:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1.- Using pre-claimed images&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The easiest way to claim the device is by downloading an image that automatically claims it on the first boot. Download this image from: &lt;a href="https://hub.pantacor.com/download-image"&gt;https://hub.pantacor.com/download-image&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2.- &lt;strong&gt;Manual claiming&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Another way to claim is by following the &lt;a href="https://www.pantavisor.io/guides/getting_started/"&gt;getting started guide&lt;/a&gt; on pantavisor.io and then claiming the device manually with the PVR CLI from your development computer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pvr device scan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result of that command should look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sergiomarin@penguin:~$ pvr device scan
Scanning ...
        ID: 61147e8af0e0f5000a50d11c (unclaimed)
        Host: localhost.local.
        IPv4: [192.168.68.115]
        IPv6: [fe80::d4ac:9722:ab19:7b81]
        Port: 22
        Claim Cmd: pvr claim -c marginally-optimum-midge https://api.pantahub.com:443/devices/61147e8af0e0f5000a50d11c
Pantavisor devices detected in network: 1 (see above for details)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And after that take the Claim Cmd value and run that. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pvr claim -c marginally-optimum-midge [https://api.pantahub.com:443/devices/61147e8af0e0f5000a50d11c](https://api.pantahub.com:443/devices/61147e8af0e0f5000a50d11c)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this step, you will be able to see your claimed device at &lt;a href="https://hub.pantacor.com/"&gt;hub.pantacor.com&lt;/a&gt; on the devices page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_1eb6KnK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AHT4rvCHSBXNdEOwZ" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_1eb6KnK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AHT4rvCHSBXNdEOwZ" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will see a new device with the status &lt;strong&gt;SYNCING. ** , This means your device has been claimed and is uploading to the cloud. When that process is **DONE,&lt;/strong&gt; you will be able to clone and modify the device.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--R_zKt2IM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AVRFaxcaZkRL9HJpf" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--R_zKt2IM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AVRFaxcaZkRL9HJpf" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  #2 Cloning the device URL to your computer
&lt;/h3&gt;

&lt;p&gt;In the device details panel, you will see the &lt;strong&gt;Clone URL&lt;/strong&gt;. Copy the URL and run the following command on your development laptop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pvr clone CLONE_URL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pvr clone [https://pvr.pantahub.com/highercomve/specially\_brief\_monkey](https://pvr.pantahub.com/highercomve/specially_brief_monkey)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That creates a new folder called &lt;code&gt;**specially\_brief\_monkey&lt;/code&gt;** much like the way &lt;code&gt;git clone&lt;/code&gt; works. Inside the folder you should see a structure similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;specially_brief_monkey
├── awconnect
│ ├── lxc.container.conf
│ ├── root.squashfs
│ ├── root.squashfs.docker-digest
│ ├── run.json
│ └── src.json
├── bsp
│ ├── addon-plymouth.cpio.xz4
│ ├── build.json
│ ├── firmware.squashfs
│ ├── kernel.img
│ ├── modules.squashfs
│ ├── pantavisor
│ ├── run.json
│ └── src.json
├── _hostconfig
│ └── pvr
│ └── docker.json
├── network-mapping.json
├── pv-avahi
│ ├── lxc.container.conf
│ ├── root.squashfs
│ ├── root.squashfs.docker-digest
│ ├── run.json
│ └── src.json
├── pvr-sdk
│ ├── lxc.container.conf
│ ├── root.squashfs
│ ├── root.squashfs.docker-digest
│ ├── run.json
│ └── src.json
└── storage-mapping.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is how Pantavisor defines a device with its running containers: :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;BSP&lt;/strong&gt; : In embedded systems, a board support package (BSP) is the layer of software containing hardware-specific drivers and other routines that allow a particular operating system to function in a particular hardware environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;awconnect&lt;/strong&gt; : Pantavisor base platform for the device and where the network configuration is and is based in this &lt;a href="https://gitlab.com/pantacor/pv-platforms/wifi-connect"&gt;container&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pv-avahi&lt;/strong&gt; : a container that sends all the information needed to discover the device in the network using pvr device scan and uses the avahi protocol for that. You can see the source container here: &lt;a href="https://gitlab.com/pantacor/pv-platforms/pv-avahi"&gt;https://gitlab.com/pantacor/pv-platforms/pv-avahi&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pv-sdk&lt;/strong&gt; : container with Pantavisor SDK that allows you to maintain the device directly from the device and is the one in charge of managing the ssh connections to the device and to the container inside it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every one of those containers will have inside a &lt;strong&gt;src.json&lt;/strong&gt; file that describes the source from where the container was created as well as some of the Pantavisor-specific configurations to run the container.&lt;/p&gt;

&lt;h3&gt;
  
  
  #3 Adding adguard to our device
&lt;/h3&gt;

&lt;p&gt;We are going to use the &lt;a href="https://hub.docker.com/r/adguard/adguardhome"&gt;adguard docker container&lt;/a&gt; as the source for our Pantavisor container (basically an LXC container). In order to add a new container from Docker inside the folder created by the &lt;strong&gt;pvr clone&lt;/strong&gt; process, we are going to run the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pvr app add --from=adguard/adguardhome:latest adguard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That creates a new folder called adguard inside the device definition with the following structure inside:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;adguard/
├── lxc.container.conf
├── root.squashfs
├── root.squashfs.docker-digest
├── run.json
└── src.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The src.json should look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "#spec": "service-manifest-src@1",
  "template": "builtin-lxc-docker",
  "args": {
   "PV_RUNLEVEL": "app"
  },
  "config": {},
  "docker_name": "adguard/adguardhome",
  "docker_tag": "latest",
  "docker_digest": "adguard/adguardhome@sha256:cd5e6641e969ec8a1df1ed02dc969db49d6cf540055f14346d0d5d42951f75d6",
  "docker_source": "remote,local",
  "docker_platform": "linux/arm",
  "persistence": {}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  #4 Configuring parameters for LXC features and Adguard persistence
&lt;/h3&gt;

&lt;p&gt;For now, we will discuss only two parts of this file: &lt;strong&gt;args&lt;/strong&gt; and &lt;strong&gt;persistence.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;args:&lt;/strong&gt; This is a configuration for the pvr cli tooling that sets up some features for the LXC container.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;persistence&lt;/strong&gt; : Configuration for the volumes and persistence for the running container. By default, pvr automatically adds all the volumes defined in the Dockerfile. You can add more volumes here, even if they aren’t defined in the Dockerfile.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As discussed, pvr adds all the containers by default to the host network namespace. As a result, the first configuration that we need to do is isolate this container inside the LXC bridge network.&lt;/p&gt;

&lt;p&gt;To do that we need to add a couple of new arguments inside the args configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PV_LXC_NETWORK_TYPE&lt;/strong&gt; : This parameter configures &lt;strong&gt;lxc.net.[0].type&lt;/strong&gt; and other parameters inside the LXC configuration depending on the value we assign in there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PV_LXC_NETWORK_IPV4_ADDRESS&lt;/strong&gt; : This is going to be the assigned IP address for the virtual network interface.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The resulting &lt;strong&gt;args&lt;/strong&gt; will be as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"args": {
    "PV_LXC_NETWORK_IPV4_ADDRESS": "10.0.3.20/24",
    "PV_LXC_NETWORK_TYPE": "veth",
    "PV_RUNLEVEL": "app"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to know more about what arguments can be used, you can read the source code of our &lt;a href="https://gitlab.com/pantacor/pvr/-/blob/develop/templates/builtin-lxc-docker.go"&gt;template to build lxc configurations&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And for persistence, we need to add the &lt;strong&gt;/opt/adguardhome/work&lt;/strong&gt; and the &lt;strong&gt;/opt/adguardhome/conf&lt;/strong&gt; folders. Both of these are used by adguard in order to save the configuration of the service.&lt;/p&gt;

&lt;p&gt;The resulting persistence configuration will be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"persistence": {
    "/opt/adguardhome/conf/": "permanent",
    "/opt/adguardhome/work/": "permanent"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  #5 Install the Adguard App
&lt;/h3&gt;

&lt;p&gt;With the persistence set up and the LXC features enabled, , you are ready to install the Adguard application using  &lt;strong&gt;pvr.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First, check that the finalized src.json looks similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "#spec": "service-manifest-src@1",
  "args": {
    "PV_LXC_NETWORK_IPV4_ADDRESS": "10.0.3.20/24",
    "PV_LXC_NETWORK_TYPE": "veth",
    "PV_RUNLEVEL": "app"
  },
  "config": {},
  "docker_digest": "adguard/adguardhome@sha256:cd5e6641e969ec8a1df1ed02dc969db49d6cf540055f14346d0d5d42951f75d6",
  "docker_name": "adguard/adguardhome",
  "docker_platform": "linux/arm",
  "docker_source": "remote,local",
  "docker_tag": "latest",
  "persistence": {
    "/opt/adguardhome/conf/": "permanent",
    "/opt/adguardhome/work/": "permanent"
  },
  "template": "builtin-lxc-docker"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your &lt;code&gt;src.json&lt;/code&gt; looks fine, let’s proceed with the installation using this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pvr app install adguard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In order to see how Adguard is installed and isolated, push the changes to the device:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pvr add &amp;amp;&amp;amp; pvr commit &amp;amp;&amp;amp; pvr post -m "Install adguard into the device"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this you will be able to see a new revision in the device at &lt;a href="https://hub.pantacor.com/"&gt;hub.pantacor.com&lt;/a&gt;, and similar to this one:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SXcMlRWn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2Ay5ddVcio0gJZF657" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SXcMlRWn--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2Ay5ddVcio0gJZF657" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the device status is &lt;strong&gt;DONE&lt;/strong&gt; or &lt;strong&gt;UPDATED,&lt;/strong&gt; the process is finished. The device should be running Adguard now, but because it is isolated, you won’t be able to use the web UI or even use it as DNS.&lt;/p&gt;

&lt;h3&gt;
  
  
  #6 Mapping the Adguard ports with awall
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;awconnect&lt;/strong&gt; platform uses iptables to manage firewall and is the one that manages to route, and &lt;strong&gt;awall&lt;/strong&gt; is a utility to generate iptables and routes based on JSON configuration.&lt;/p&gt;

&lt;p&gt;This is a good &lt;a href="https://wiki.alpinelinux.org/wiki/Zero-To-Awall"&gt;guide&lt;/a&gt; about how awall works. I will not enter the deepest lands of awall, instead, I will show how to use it for this use case.&lt;/p&gt;

&lt;h3&gt;
  
  
  Configure Awall for Pantavisor
&lt;/h3&gt;

&lt;p&gt;First, let’s start by adding this small utility script to run awall without installing it on your computer, and instead, run it from a Docker container. You can see and download the script &lt;a href="https://gist.githubusercontent.com/highercomve/295cf75fb660be4c6b054627c330cb4b/raw/d51989dd5c57c36136814033af6d460db3024bef/awall2pvmwall"&gt;here&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wget https://gist.githubusercontent.com/highercomve/295cf75fb660be4c6b054627c330cb4b/raw/d51989dd5c57c36136814033af6d460db3024bef/awall2pvmwall &amp;amp;amp;&amp;amp;amp; chmod +x awall2pvmwall
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That script runs awall for the &lt;code&gt;awall.json&lt;/code&gt; file in the root of the device folder as well as any &lt;code&gt;_awall configuration&lt;/code&gt; folder inside any application or platform of the device.&lt;/p&gt;

&lt;p&gt;We are going to create several JSON files to configure awall. Port_forward_example is the folder name for where my device repository lives and also where we will add all the new files for configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir -p adguard/_awall
touch awall.json
touch adguard/_awall/config.json
touch bsp/_awall.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The configuration file bsp/_awall.json defines a couple of variables for our device.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
   "variable": {
    "containernet_if": "lxcbr0",
    "wan_if": "eth0",
    "lan_if": "wlan0"
   }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are the default values that Pantavisor sets for a Raspberry PI 4. If your Raspberry Pi is connected to the Internet via an ethernet cable, the &lt;strong&gt;wan_if&lt;/strong&gt; will be &lt;strong&gt;eth0&lt;/strong&gt; and maybe your wifi will be a local area network. In some cases, you may be connected via wifi to an Internet provider and will use the eth0 as an entry point to your local network.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;awall.json&lt;/strong&gt; configuration file defines the general firewall rules for the device. We are going to add to that file everything that is directly related to the host network namespace.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
   "description": "How to use awall",
   "filter": [
       {
           "action": "accept",
           "in": "internet",
           "service": [
               "ping",
               "dns",
               "ssh",
               "dhcp",
               "pvssh"
           ]
       },
       {
           "action": "accept",
           "in": "intranet",
           "service": [
               "dns",
               "ping",
               "ssh",
               "dhcp",
               "pvssh"
           ]
       },
       {
           "action": "accept",
           "in": "internet",
           "out": "_fw",
           "service": [
               "ssh",
               "dhcp",
               "pvssh"
           ]
       },
       {
           "action": "accept",
           "out": "internet",
           "service": [
               "http",
               "https"
           ]
       }
   ],
   "import": [
       "bsp"
   ],
   "policy": [
       {
           "action": "accept",
           "out": "internet"
       },
       {
           "action": "drop",
           "in": "internet"
       },
       {
           "action": "accept",
           "out": "intranet"
       },
       {
           "action": "drop",
           "in": "intranet"
       },
       {
           "action": "accept",
           "in": "containernet"
       },
       {
           "action": "accept",
           "out": "containernet"
       },
       {
           "action": "reject"
       }
   ],
   "service": {
       "pvssh": [
           {
               "port": 8222,
               "proto": "tcp"
           }
       ]
   },
   "snat": [
       {
           "out": "internet"
       }
   ],
   "zone": {
       "containernet": {
           "iface": "$containernet_if"
       },
       "internet": {
           "iface": "$wan_if"
       },
       "intranet": {
           "iface": "$lan_if"
       }
   }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this configuration, we open access to some ports of the device:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ssh&lt;/strong&gt; : port 22 to access the &lt;strong&gt;pvr-sdk&lt;/strong&gt; container&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pvssh&lt;/strong&gt; : port 8222 to enter directly to a container&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;dns&lt;/strong&gt; : port 53&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ping:&lt;/strong&gt; icmp port&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;dhcp:&lt;/strong&gt; port 67 and 68&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  #7 Redirect traffic to the Adguard IP with DNAT
&lt;/h3&gt;

&lt;p&gt;Now let’s redirect everything to the Adguard IP address by using a DNAT configuration. We will need to populate the &lt;code&gt;**aguard/\_awall/config.json&lt;/code&gt;** file with this configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
   "description": "Forward All need it ports to this container",
   "filter": [
       {
           "action": "accept",
           "dnat": "10.0.3.20",
           "in": "internet",
           "service": "adguard-web"
       },
       {
           "action": "accept",
           "dnat": "10.0.3.20",
           "in": "intranet",
           "service": "adguard-web"
       },
       {
           "action": "accept",
           "dnat": "10.0.3.20",
           "in": "internet",
           "service": "adguard-dotls"
       },
       {
           "action": "accept",
           "dnat": "10.0.3.20",
           "in": "intranet",
           "service": "adguard-dotls"
       },
       {
           "action": "accept",
           "dnat": "10.0.3.20",
           "in": "internet",
           "service": "adguard-dnsencrypt"
       },
       {
           "action": "accept",
           "dnat": "10.0.3.20",
           "in": "intranet",
           "service": "adguard-dnsencrypt"
       }
   ],
   "service": {
       "adguard-web": [
           {
               "port": 3000,
               "proto": "tcp"
           },
           {
               "port": 3000,
               "proto": "udp"
           }
       ],
       "adguard-dotls": [
           {
               "port": 53,
               "proto": "tcp"
           },
           {
               "port": 53,
               "proto": "udp"
           },
           {
               "port": 853,
               "proto": "tcp"
           },
           {
               "port": 853,
               "proto": "udp"
           },
           {
               "port": 784,
               "proto": "udp"
           },
           {
               "port": 8853,
               "proto": "udp"
           },
           {
               "port": 5443,
               "proto": "tcp"
           },
           {
               "port": 8853,
               "proto": "udp"
           }
       ],
       "adguard-dnsencrypt": [
           {
               "port": 5443,
               "proto": "tcp"
           },
           {
               "port": 8853,
               "proto": "udp"
           }
       ]
   }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There, you can see the ports grouped by service. All of those services will be translated from both the WAN and the LAN interface to the container IP.&lt;/p&gt;

&lt;h3&gt;
  
  
  #8 Setting up the port forwarding and viewing the Adguard dashboard
&lt;/h3&gt;

&lt;p&gt;Now that we have all the JSON configuration ready, we can run the &lt;strong&gt;awall2pvmwall&lt;/strong&gt; script that will create a folder with this structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_config/
└── awconnect
    └── etc
        └── iptables
            ├── dump
            ├── rules.v4
            └── rules.v6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this we can now post this new revision to our device:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pvr add .
pvr commit
pvr post -m "Update awconnect with port forwading to adguard"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---pM8wquW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2ATIQzIG8RcHg5NbJn" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---pM8wquW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2ATIQzIG8RcHg5NbJn" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4gWm1584--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AksoKJC85K_TiUlaD" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4gWm1584--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AksoKJC85K_TiUlaD" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE: In here it is important to move from port 80 to 3000&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4STTaPU3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AeRc17ktGqfwrLhts" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4STTaPU3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2AeRc17ktGqfwrLhts" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After a couple of more next and next, you will be able to enter the adguard dashboard and use your Raspberry PI as DNS for your entire network and will also be blocking ads,using an upstream DNS via DoT and a lot more..&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--m1tfatKG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2Avbv3vTQ7QQeuVpwq" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--m1tfatKG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1024/0%2Avbv3vTQ7QQeuVpwq" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is a&lt;a href="https://kb.adguard.com/general/dns-providers"&gt;list of known DNS providers&lt;/a&gt; to choose from.&lt;/p&gt;

&lt;p&gt;You can see the device I built for this guide in &lt;a href="https://hub.pantacor.com/u/highercomve/devices/611130a5415a95000acf2bca"&gt;here&lt;/a&gt; or you could clone with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pvr clone [https://pvr.pantahub.com/highercomve/port\_fordward\_example](https://pvr.pantahub.com/highercomve/port_fordward_example)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now go and play with Pantavisor and containers and may the force be with you.&lt;/p&gt;




</description>
      <category>raspberrypi</category>
      <category>portforwarding</category>
      <category>lxc</category>
    </item>
    <item>
      <title>How I explain to my friends what we do at work</title>
      <dc:creator>Sergio Marin</dc:creator>
      <pubDate>Thu, 25 Jun 2020 22:17:51 +0000</pubDate>
      <link>https://forem.com/highercomve/how-i-explain-to-my-friends-what-we-do-at-work-2pj0</link>
      <guid>https://forem.com/highercomve/how-i-explain-to-my-friends-what-we-do-at-work-2pj0</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---VsIZWOR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/806/0%2AFwSExMifj1LW1JJz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---VsIZWOR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/806/0%2AFwSExMifj1LW1JJz.png" alt="" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pantahub and Pantavisor could sometimes be a difficult paradigm to understand because, in a way, it changes how we are related to the firmware and software of devices.&lt;/p&gt;

&lt;p&gt;Today, when you don’t do something with an embedded device, maybe an IoT project or home automation with your Raspberry, you will go and flash your device with a Linux distribution and start to install packages and configure all of them via ssh, and if you need to replicate that you will do it all over again. Then if you want to automate that process a little bit you may create a shell script to do all the configuration and installation.&lt;/p&gt;

&lt;p&gt;What happens if you have updated that script and you then need to update that configuration (let’s say you have only two devices), you need to go and run everything on both devices, hopefully via SSH. You can do some kind of more sophisticated thing here, there are a lot of tools for that, but maybe you are running this on a device with very little memory and your plans can fall apart.&lt;/p&gt;

&lt;p&gt;But what we want to do is see devices and their configuration as you see Docker images and Docker containers, but you don’t even need to install and configure the device once. You only need to flash it with Pantahub. and then post revisions that will be deployed on the device. You can even set a device as leader of a pack, and the whole pack will follow the leader revisions automatically.&lt;/p&gt;

&lt;p&gt;Let’s start by understanding all this terminology: devices, revisions, platforms, etc. For that let’s talk more about Pantahub and Pantavisor.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Pantahub?
&lt;/h3&gt;

&lt;p&gt;Pantahub offers cloud-controlled firmware and software management for all kinds of Linux-based connected devices. In addition to this, Pantahub provides a community ecosystem for device makers, operators, and technology enthusiasts to gather around and share their work.&lt;/p&gt;

&lt;p&gt;To use Pantahub, you will need to install Pantavisor on your devices.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Pantavisor?
&lt;/h3&gt;

&lt;p&gt;Pantavisor is the Linux device init system that turns all the runtime into a set of container micro functions. Through Pantavisor, devices become software-defined infrastructure, through Linux container technology. All Pantavisor enabled devices can automatically connect to a Pantahub instance.&lt;/p&gt;

&lt;p&gt;By using these two technologies you will be able to manage your devices and the software in a way similar (similar but not the same) to how you manage applications with docker.&lt;/p&gt;

&lt;p&gt;However, instead of having a container running on the device, the device will run the &lt;strong&gt;PLATFORM&lt;/strong&gt; directly on metal, allowing you to have versions of the device. This device state version is called &lt;strong&gt;REVISION&lt;/strong&gt; , and inside Pantahub you will have the ability to return to a previous revision and, more importantly, the system is fault-tolerant. That means if you deploy a revision that can’t run correctly on the device, it will start the last revision that worked correctly with that, you can be sure that you won’t lose your device if you configure something in the wrong way or if the software has any kind of problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  How the ecosystem works
&lt;/h3&gt;

&lt;p&gt;The ecosystem could look similar to how you see containers on the Docker hub, but instead of containers, you will see devices.&lt;/p&gt;

&lt;p&gt;Here you can see how a device is shown inside Pantahub.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--I1l0vnkj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.imgur.com/nvOVwxK.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--I1l0vnkj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://i.imgur.com/nvOVwxK.png" alt="" width="800" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A device in Pantahub represents a physical device, in this example a raspberry pi 3 b+ running several &lt;strong&gt;PLATFORMS&lt;/strong&gt; /APPS inside.&lt;/p&gt;

&lt;p&gt;Those &lt;strong&gt;PLATFORMS&lt;/strong&gt; are built from a docker container, but inside of the device there isn’t any Docker runtime.&lt;/p&gt;

&lt;p&gt;In this example you see a device running:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;cloudflare&lt;/strong&gt; : Platform installed by &lt;a href="https://www.pantahub.com/blog/how-pantahub-and-pantavisor-works/one.apps.pantahub.com"&gt;one.apps.pantahub.com&lt;/a&gt; to manage the DNS configuration and to set the DNS filters for your WIFI hotspot&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;awconnect&lt;/strong&gt; : Manages the network and the wifi connections.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pv-avahi&lt;/strong&gt; : Exposes an mDns configuration in order to be able to discover any Pantahub enabled device on the network using the PVR CLI or the Mobile application.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pvr-sdk&lt;/strong&gt; : Manages some of the functionalities of the device like the SSH connection, the ability to follow devices, read user metadata and so much more.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this, you can notice how a device is composed of several platforms and those platforms are based on docker containers. But remember there is not any docker container running on the device.&lt;/p&gt;

&lt;p&gt;The device revision is described as a JSON file that is converted to a file system when you clone the device.&lt;/p&gt;

&lt;p&gt;Let me explain with a visual example using the same device.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cHJXrpAW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.pantahub.com/blog/img/image1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cHJXrpAW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.pantahub.com/blog/img/image1.png" alt="" width="766" height="750"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We call this JSON specification of the device revision state.json&lt;/p&gt;

&lt;p&gt;As you can see the &lt;strong&gt;STATE&lt;/strong&gt; is a normalized JSON with the folder structure of the device, where any .json file inside that structure is part of state.json and any other file format is represented by the sha256 of that file and is saved in Pantahub objects (our storage in the cloud)&lt;/p&gt;

&lt;p&gt;When you use pvr clone URL_OF_DEVICE (just like a git clone), you will get the same structure as in the state.json but the files represented as sha256 are going to be downloaded.&lt;/p&gt;

&lt;p&gt;The result is going to be a folder like this one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--hYfk6_7_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.pantahub.com/blog/img/image2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--hYfk6_7_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.pantahub.com/blog/img/image2.png" alt="" width="419" height="928"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every platform lives in its own folder and all the configuration files live inside of the _config/${PLATFORM} folder&lt;/p&gt;

&lt;p&gt;If you want to dive deep in all the available endpoints you can read the API reference &lt;a href="https://docs.pantahub.com/pantahub-base/devices/"&gt;here&lt;/a&gt; in the documentation or the swagger documentation &lt;a href="https://api.pantahub.com/swagger/index.html"&gt;here&lt;/a&gt;&lt;/p&gt;

</description>
      <category>containers</category>
      <category>development</category>
      <category>frontend</category>
    </item>
    <item>
      <title>You may need Laziness in your Javascript</title>
      <dc:creator>Sergio Marin</dc:creator>
      <pubDate>Fri, 22 Feb 2019 22:47:31 +0000</pubDate>
      <link>https://forem.com/highercomve/you-may-need-laziness-in-your-javascript-oej</link>
      <guid>https://forem.com/highercomve/you-may-need-laziness-in-your-javascript-oej</guid>
      <description>&lt;p&gt;&lt;a href="https://itnext.io/you-may-need-laziness-in-your-javascript-f03e8a2d4629?source=rss-ea85e17e7579------2"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8TucDVOS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1240/1%2Ay1iHc6IWc-ZjNVQgvTLCxQ.jpeg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Maybe some times you have overused the Array methods map, reduce, filter, find, etc. and that could make out applications to use more…&lt;/p&gt;

&lt;p&gt;&lt;a href="https://itnext.io/you-may-need-laziness-in-your-javascript-f03e8a2d4629?source=rss-ea85e17e7579------2"&gt;Continue reading on ITNEXT »&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>enumerable</category>
      <category>iteration</category>
      <category>node</category>
    </item>
    <item>
      <title>Back to the Browser: React Form Validation with the DOM API</title>
      <dc:creator>Sergio Marin</dc:creator>
      <pubDate>Mon, 18 Feb 2019 21:28:49 +0000</pubDate>
      <link>https://forem.com/highercomve/back-to-the-browser-react-form-validation-with-the-dom-api-g2i</link>
      <guid>https://forem.com/highercomve/back-to-the-browser-react-form-validation-with-the-dom-api-g2i</guid>
      <description>&lt;p&gt;&lt;a href="https://itnext.io/back-to-the-browser-form-validation-d32dd01802c0?source=rss-ea85e17e7579------2"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--r2Gho3fR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1132/1%2AlOBid_kZry-I0rkPpu7Slg.png" alt="" width="800" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Last week I wrote an article about how we can build a form validation hook with React Hooks (more about it here)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://itnext.io/back-to-the-browser-form-validation-d32dd01802c0?source=rss-ea85e17e7579------2"&gt;Continue reading on ITNEXT »&lt;/a&gt;&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>javascript</category>
      <category>react</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Form validation with React Hooks</title>
      <dc:creator>Sergio Marin</dc:creator>
      <pubDate>Thu, 14 Feb 2019 14:52:44 +0000</pubDate>
      <link>https://forem.com/highercomve/form-validation-with-react-hooks-1om</link>
      <guid>https://forem.com/highercomve/form-validation-with-react-hooks-1om</guid>
      <description>&lt;p&gt;&lt;a href="https://itnext.io/form-validation-with-react-hooks-ab0dbba23b9f?source=rss-ea85e17e7579------2"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qNmpIj6t--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/903/1%2AYWaSaYPtW1n_vEplTUaanA.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you may know, I’m pretty excited about the new React API for Hooks (here is an introduction about them)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://itnext.io/form-validation-with-react-hooks-ab0dbba23b9f?source=rss-ea85e17e7579------2"&gt;Continue reading on ITNEXT »&lt;/a&gt;&lt;/p&gt;

</description>
      <category>validation</category>
      <category>react</category>
      <category>reacthook</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Why I’m excited with React Hooks?</title>
      <dc:creator>Sergio Marin</dc:creator>
      <pubDate>Mon, 11 Feb 2019 19:34:47 +0000</pubDate>
      <link>https://forem.com/highercomve/why-i-m-excited-with-react-hooks-1efe</link>
      <guid>https://forem.com/highercomve/why-i-m-excited-with-react-hooks-1efe</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rc_VMMaF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2ALaTU0GpA_arBSXOmuXdwuQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rc_VMMaF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://cdn-images-1.medium.com/max/1024/1%2ALaTU0GpA_arBSXOmuXdwuQ.png" alt="" width="800" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On February 6 React 16.8 was released and with React 16.8, &lt;a href="https://reactjs.org/docs/hooks-intro.html"&gt;React Hooks&lt;/a&gt; are available in a stable release!&lt;/p&gt;

&lt;p&gt;That means we can start using Hooks without the fear of writing unstable code.&lt;/p&gt;

&lt;p&gt;Here is a live example of the code in this post:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://frontarm.com/demoboard/?id=af4f455f-1d30-4823-90a9-b15cfc3e92f9"&gt;https://frontarm.com/demoboard/?id=af4f455f-1d30-4823-90a9-b15cfc3e92f9&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  But, what are Hooks?
&lt;/h3&gt;

&lt;p&gt;Hooks are functions that allow you to use state and “life cycle” for functional Components, that means you don’t need to write classes in order to build React components.&lt;/p&gt;

&lt;p&gt;For me, this is exciting because I don’t like to bind “this” (you saw what I did there?). And what I mean is, that Classes in React are kind of messy (I'm not the only one who think that). Most of the time it has something to do with “this”. But that is a story for another time.&lt;/p&gt;

&lt;p&gt;So you get access almost all the state lifecycle without using classes. But how?&lt;/p&gt;

&lt;h3&gt;
  
  
  Which Hooks are available and what they do?
&lt;/h3&gt;

&lt;p&gt;Directly from React’s page, we have this list of Hooks (&lt;a href="https://reactjs.org/docs/hooks-reference.html"&gt;https://reactjs.org/docs/hooks-reference.html&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://reactjs.org/docs/hooks-reference.html#basic-hooks"&gt;Basic Hooks&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://reactjs.org/docs/hooks-reference.html#usestate"&gt;useState&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://reactjs.org/docs/hooks-reference.html#useeffect"&gt;useEffect&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://reactjs.org/docs/hooks-reference.html#usecontext"&gt;useContext&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://reactjs.org/docs/hooks-reference.html#additional-hooks"&gt;Additional Hooks&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://reactjs.org/docs/hooks-reference.html#usereducer"&gt;useReducer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://reactjs.org/docs/hooks-reference.html#usecallback"&gt;useCallback&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://reactjs.org/docs/hooks-reference.html#usememo"&gt;useMemo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://reactjs.org/docs/hooks-reference.html#useref"&gt;useRef&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://reactjs.org/docs/hooks-reference.html#useimperativehandle"&gt;useImperativeHandle&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://reactjs.org/docs/hooks-reference.html#uselayouteffect"&gt;useLayoutEffect&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://reactjs.org/docs/hooks-reference.html#usedebugvalue"&gt;useDebugValue&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I will try to start with the first 3 elements of that list. (at the beginning I was thinking in 4 but the post got too long, I will split it)&lt;/p&gt;

&lt;h3&gt;
  
  
  useState Hook
&lt;/h3&gt;

&lt;p&gt;This hook lets you have an internal state inside a functional Component. Here is a small comparison side by side of a counter component written as Class Component and as Function Component with useState.&lt;/p&gt;

&lt;p&gt;Let's use a counter as an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React from 'react'

export default class Counter extends React.Component {
  constructor (props) {
    super(props)
    this.state = {
      count: props.initialCount || 0
    }
    this.add = this.add.bind(this)
    this.minus = this.minus.bind(this)
    this.reset = this.reset.bind(this)
  }
  add () {
    this.setState((state) =&amp;gt; ({ count: state.count + 1 }))
  }
  minus () {
    this.setState((state) =&amp;gt; ({ count: state.count - 1 }))
  }
  reset () {
    this.setState((state) =&amp;gt; ({ count: 0 }))
  }
  render () {
    return (
      &amp;lt;div className="counter"&amp;gt;
        &amp;lt;span className="counter\_\_count"&amp;gt;{ this.state.count }&amp;lt;/span&amp;gt;
        &amp;lt;button onClick={this.add}&amp;gt;+&amp;lt;/button&amp;gt;
        &amp;lt;button onClick={this.minus}&amp;gt;-&amp;lt;/button&amp;gt;
        &amp;lt;button onClick={this.reset}&amp;gt;Clear&amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    )
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the same but with useState&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useState, useEffect } from 'react'

export default function CounterUseState (props) {
  const [count, setCount] = useState(props.initialCount || 0)
  const add = () =&amp;gt; { setCount(count + 1) }
  const minus = () =&amp;gt; { setCount(count - 1) }
  const reset = () =&amp;gt; { setCount(0) }

  return (
      &amp;lt;div className="counter"&amp;gt;
        &amp;lt;span className="counter\_\_count"&amp;gt;{ count }&amp;lt;/span&amp;gt;
        &amp;lt;button onClick={add}&amp;gt;+&amp;lt;/button&amp;gt;
        &amp;lt;button onClick={minus}&amp;gt;-&amp;lt;/button&amp;gt;
        &amp;lt;button onClick={reset}&amp;gt;Clear&amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We don’t have more “this” anymore! as result the “state” is easier to use, the handlers are cleaner and it would be easier to compose with functions outside of the component scope.&lt;/p&gt;

&lt;p&gt;Now how we can handle with the “life cycle” of the component? we will use the &lt;strong&gt;useEffect&lt;/strong&gt; Hook&lt;/p&gt;

&lt;h3&gt;
  
  
  useEffect Hook
&lt;/h3&gt;

&lt;p&gt;This Hook received two parameters, the first argument is a callback function to run when the component is rendered, the callback will be called either is the first time the component is rendered ( &lt;strong&gt;componentDidMount&lt;/strong&gt; ) or every time it’s re-rendered ( &lt;strong&gt;componentDidUpdate&lt;/strong&gt; ), the second parameter is very important for this because with determines what variables or props you want to “observe” for changes. Therefore using useEffect will work as &lt;strong&gt;componentDidMount&lt;/strong&gt; and &lt;strong&gt;componentDidUpdate&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And how we clear those effects? well, the function that runs in the effect should return another function that is gonna we ran when the component is unmounted. Magic!!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useState, useEffect } from 'react'

export default function FunctionalTimer (props) {
  const [count, setCount] = useState(props.initialCount || 0)
  const [running, setRunning] = useState(false)

  const start = () =&amp;gt; setRunning(true)
  const pause = () =&amp;gt; setRunning(false)
  const reset = () =&amp;gt; setCount(0)
  const tick = () =&amp;gt; running &amp;amp;&amp;amp; setTimeout(() =&amp;gt; setCount(count + 1), 1000)

  useEffect(() =&amp;gt; {
    tick()
  }, [running, count])

return (
      &amp;lt;div className="counter"&amp;gt;
        &amp;lt;span className="counter\_\_count"&amp;gt;{ count }&amp;lt;/span&amp;gt;
        &amp;lt;button onClick={start}&amp;gt;Start&amp;lt;/button&amp;gt;
        &amp;lt;button onClick={pause}&amp;gt;Pause&amp;lt;/button&amp;gt;
        &amp;lt;button onClick={reset}&amp;gt;Clear&amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;setTimeout&lt;/strong&gt; and &lt;strong&gt;setInterval&lt;/strong&gt; are effects outside of the pure nature of the functional component. The same applies to things that are changing other things outside the component, for example, the document.title or adding an event listener to a not synthetic event.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useState, useEffect } from 'react'

export default function FunctionalTimer (props) {
  const [count, setCount] = useState(props.initialCount || 0)
  const [running, setRunning] = useState(false)

  const start = () =&amp;gt; setRunning(true)
  const pause = () =&amp;gt; setRunning(false)
  const reset = () =&amp;gt; setCount(0)
  const tick = () =&amp;gt; running &amp;amp;&amp;amp; setTimeout(() =&amp;gt; setCount(count + 1), 1000)

  useEffect(() =&amp;gt; {
    tick()
  }, [running, count])

  useEffect(() =&amp;gt; {
    document.title = `${count} seconds pass`
  }, [count])

useEffect(() =&amp;gt; {
    const logOnSizeUpdate = () =&amp;gt; console.log({ count, running })
    window.addEventListener('resize', logOnSizeUpdate)
    return () =&amp;gt; window.removeEventListener('resize', logOnSizeUpdate)

  }, []) // when the array is empty only runs on mount not update

return (
      &amp;lt;div className="counter"&amp;gt;
        &amp;lt;span className="counter\_\_count"&amp;gt;{ count }&amp;lt;/span&amp;gt;
        &amp;lt;button onClick={start}&amp;gt;Start&amp;lt;/button&amp;gt;
        &amp;lt;button onClick={pause}&amp;gt;Pause&amp;lt;/button&amp;gt;
        &amp;lt;button onClick={reset}&amp;gt;Clear&amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One of the awesome abilities of Hooks is the ability to create Custom Hooks, and custom hooks are just simple functions!&lt;/p&gt;

&lt;p&gt;Let's rewrite that timer code and move all the state logic to custom hooks called &lt;strong&gt;useTimer&lt;/strong&gt; and &lt;strong&gt;useSetTitle&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useState, useEffect } from 'react'

function useTimer (initialCount = 0, autoStart = false) {
  const [count, setCount] = useState(initialCount)
  const [running, setRunning] = useState(autoStart)

const start = () =&amp;gt; setRunning(true)
  const pause = () =&amp;gt; setRunning(false)
  const reset = () =&amp;gt; setCount(0)
  const tick = () =&amp;gt; running &amp;amp;&amp;amp; setTimeout(() =&amp;gt; setCount(count + 1), 1000)

  useEffect(() =&amp;gt; {
    tick()
  }, [running, count])

  return {
    count,
    running,
    start,
    pause,
    reset
  }
}

function useSetTitle (count) {
  useEffect(() =&amp;gt; {
    document.title = `${count} seconds pass`
  })
}

export default function FunctionalTimer (props) {
  const timer = useTimer(props.initialCount, props.autoStart)
  useSetTitle(timer.count)

  return (
      &amp;lt;div className="counter"&amp;gt;
        &amp;lt;span className="counter\_\_count"&amp;gt;{ timer.count }&amp;lt;/span&amp;gt;
        &amp;lt;button onClick={timer.start}&amp;gt;Start&amp;lt;/button&amp;gt;
        &amp;lt;button onClick={timer.pause}&amp;gt;Pause&amp;lt;/button&amp;gt;
        &amp;lt;button onClick={timer.reset}&amp;gt;Clear&amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows us to build a bunch of stateful logic that live in Hooks and can be used in any functional component, enabling us to reuse and compose in a better way our code.&lt;/p&gt;

&lt;h3&gt;
  
  
  useRef
&lt;/h3&gt;

&lt;p&gt;This hook allows us to have a persistent value between components render. And why we could need that, if you notice in the previous example the way the time is working is putting on setTimeout the next counter change. The problem with that is if we clear the timer, the next time the function that will be run, will have the value of count before the clear, and because you need to pause, clear and start again. Functions references in the effect will have the value of the variables at the moment they were set. That way we will need &lt;strong&gt;useRef&lt;/strong&gt; to storage a callback every time that the component renders with the current count value that maintains the stability of the timer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useState, useEffect, useRef } from 'react'

function useTimerInterval (initialCount = 0, autoStart = false) {
  const [count, setCount] = useState(initialCount)
  const [running, setRunning] = useState(autoStart)
  const cb = useRef()
  const id = useRef()

  const start = () =&amp;gt; setRunning(true)
  const pause = () =&amp;gt; setRunning(false)
  const reset = () =&amp;gt; setCount(0)

  function callback () {
    setCount(count + 1)
  }

  // Save the current callback to add right number to the count, every render
  useEffect(() =&amp;gt; {
    cb.current = callback
  })

  useEffect(() =&amp;gt; {

    // This function will call the cb.current, that was load in the effect before. and will always refer to the correct callback function with the current count value.   
    function tick() {
      cb.current()
    }
    if (running &amp;amp;&amp;amp; !id.current) {
      id.current = setInterval(tick, 1000)
    }

    if (!running &amp;amp;&amp;amp; id.current) {
      clearInterval(id.current)
      id.current = null
    }
    return () =&amp;gt; id.current &amp;amp;&amp;amp; clearInterval(id.current)
  }, [running])

  return {
    count,
    start,
    pause,
    reset
  }
}

function useSetTitle (count) {
  useEffect(() =&amp;gt; {
    document.title = `${count} seconds pass`
  })
}

export default function FunctionalTimer (props) {
  const timer = useTimerInterval(props.initialCount, props.autoStart)
  useSetTitle(timer.count)

  return (
      &amp;lt;div className="counter"&amp;gt;
        &amp;lt;span className="counter\_\_count"&amp;gt;{ timer.count }&amp;lt;/span&amp;gt;
        &amp;lt;button onClick={timer.start}&amp;gt;Start&amp;lt;/button&amp;gt;
        &amp;lt;button onClick={timer.pause}&amp;gt;Pause&amp;lt;/button&amp;gt;
        &amp;lt;button onClick={timer.reset}&amp;gt;Clear&amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is a live example of the code in this post:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://frontarm.com/demoboard/?id=af4f455f-1d30-4823-90a9-b15cfc3e92f9"&gt;Demoboard&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And maybe you want to read a little more about why the timer needs the persistent reference. I that case this article deeps into it &lt;a href="https://overreacted.io/making-setinterval-declarative-with-react-hooks/"&gt;https://overreacted.io/making-setinterval-declarative-with-react-hooks/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a basic example of what we can do know with React Hooks and I recommend to see this video from the React Conf about Hooks.&lt;/p&gt;

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

&lt;p&gt;I will continue this topic about React Hooks with &lt;strong&gt;useContext&lt;/strong&gt; and &lt;strong&gt;useReducer&lt;/strong&gt; and how we can build something like Redux with those two Hooks.&lt;/p&gt;

&lt;p&gt;Until the next post, May the Force be with you&lt;/p&gt;




</description>
      <category>react</category>
      <category>webdev</category>
      <category>reacthook</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Esto es lo que hago y quiero seguir haciendo porque en verdad lo disfruto</title>
      <dc:creator>Sergio Marin</dc:creator>
      <pubDate>Mon, 24 Aug 2015 09:09:03 +0000</pubDate>
      <link>https://forem.com/highercomve/esto-es-lo-que-hago-y-quiero-seguir-haciendo-porque-en-verdad-lo-disfruto-31l</link>
      <guid>https://forem.com/highercomve/esto-es-lo-que-hago-y-quiero-seguir-haciendo-porque-en-verdad-lo-disfruto-31l</guid>
      <description>&lt;p&gt;Desde hace 6 años me dedico a la educación en el área de desarrollo web/móvil, como instructor día a día compruebo que las generaciones actuales son inmensamente más geniales que la mía y logran procesar información a velocidades mucho mayores a lo que lo hacíamos nosotros.&lt;/p&gt;

&lt;p&gt;Pero esa misma cantidad masiva de conocimientos e información procesada no siempre logran encontrar el camino adecuado a las buenas prácticas y metodologías que la industria actual del emprendimiento y startup del área del desarrollo web necesitan, por lo tanto si no logra conseguir una empresa lo suficientemente bueno para entender, este joven puede terminar rápidamente contratado en el trabajo menos adecuado para su capacidad (terminando agotado en una oficina aburrida y no desarrollando la innovación del mañana).&lt;/p&gt;

&lt;p&gt;Por eso es que nosotros queremos iniciar un proyecto que consta con dos componentes críticos, el primero alimentar la mentes geniales de estos jóvenes llevándolos de la mano por un camino educativo que los inicie por el camino a ser líderes de la industria en el futuro, usando como herramienta el déficit de desarrolladores actuales de la industria y el crecimiento acelerado de la misma, que muchas veces no puede atacar proyectos porque no tienen quien se los desarrolle en el tiempo correcto y con una buena calidad. Y porque esto es nuestra herramienta, porque vamos a usar esta necesidad para darle a nuestros alumnos proyectos reales que desarrollar mientras mantienen su cerebro en constante aprendizaje de las mejores tecnicas y practicas de la industria.&lt;/p&gt;

&lt;p&gt;¿Cómo lo vamos a hacer? Tres meses de entrenamiento intensivo, ¡no es un curso más! ¡No! Es un entrenamiento de guerra, ocho horas diarias, donde tu único objetivo será cambiar tu vida. En uno de los mejores espacios de emprendimiento en donde además vas a tener contacto directo con la industria, estará al lado tuyo mientras aprendes. Y por el otro lado las empresas podrán desarrollar los nuevos proyectos que desean hacer, mientras a través de un sistema web pueden hacer seguimientos y control de su producto, permitiendo que además el alumno aprenda como es el proceso y metodologías de seguimiento y construcción de un proyecto web.&lt;/p&gt;

&lt;p&gt;¿Quién lo financia? La industria, las empresas que están desarrollando proyectos con nuestros talentosos alumnos, al pagar el desarrollo de estos proyectos pagan la educación de estos alumnos.&lt;/p&gt;

&lt;p&gt;¿Por qué no cobrarle al alumno que es el interesado en aprender?, porque normalmente estos jóvenes geniales pueden aprender solo con usar internet, entrar en comunidades, youtube y toda la información disponible en internet de manera gratuita. Para no cambiarles ese modelo mental seguirán consumiendo contenido gratuito pero ahora enfocado y dirigido, metódico y directo por el camino de las buenas prácticas.&lt;/p&gt;

&lt;p&gt;Esto es nuestra propuesta de valor seguir haciendo lo que amamos, mientras alimentamos a la industria de manera acelerada de mentes jóvenes y geniales para que salgan a apoderarse de ella. Este es el proximo paso para seguir hackiando en escuelaweb.&lt;/p&gt;

&lt;p&gt;¡Gracias y que las fuerza los acompañe!&lt;/p&gt;




</description>
      <category>entrepreneurship</category>
      <category>education</category>
      <category>development</category>
    </item>
  </channel>
</rss>
