├── 1-SETUP.md ├── 2-BUILD.md ├── 3-BLINKT.md ├── 4-WEB.md ├── 5-HACK.md ├── CODING.md ├── CONCLUSION.md ├── LICENSE ├── PROXIES.md ├── README.md ├── STATIC_IP.md └── labs ├── 3.2 ├── Dockerfile └── pixel.py ├── 3.3 ├── Dockerfile └── pixel.py ├── 3.4 ├── Dockerfile └── pixel.py ├── 4.1 ├── Dockerfile └── server.py ├── 4.2 ├── Dockerfile └── server.py ├── 4.2a ├── Dockerfile └── server.py └── 5.1 ├── Dockerfile └── app.py /1-SETUP.md: -------------------------------------------------------------------------------- 1 | ## 1 - Setup 2 | 3 | ### The Pimoroni Blinkt! kit 4 | 5 | The Dockercon workshop kit was designed in conjunction with Pimoroni, a maker and educator from Sheffield in the UK. 6 | 7 | Contents: 8 | 9 | * Presentation box and stickers 10 | * Raspberry Pi Zero with soldered 40-pin header 11 | * MicroSD Card and SD card adapter 12 | * 50cm red micro-USB cable 13 | * Blinkt 14 | 15 | > The kit is yours to take home and keep. The Pi can also be powered by a phone charger with a USB WiFi or Ethernet dongle. 16 | 17 | ### 1.1 Download the Raspbian ISO 18 | 19 | > Note: if you are following this material at a conference we may make alternative arrangements to make downloading the ISO quicker such as exchanging USB keys or a local mirror. 20 | 21 | We will be using the Pi's official Operating System which comes in two flavours - Pixel (with full UI, games and utilities) and Lite (ideal for Docker and headless use). 22 | 23 | Head over to [Raspberry Pi Downloads page](https://www.raspberrypi.org/downloads/raspbian/) and click "RASPBIAN JESSIE LITE". 24 | 25 | If you're on a Mac then the file will be uncompressed when the download completes, on Windows or Linux you will need to decompress the archive. 26 | 27 | You can use [Etcher.io](https://etcher.io) flashing tool on Windows/Mac and Linux to flash your SD card. Etcher.io will perform a checksum after writing the card to make sure it is valid. 28 | 29 | You will be asked for your password on MacOS after clicking the Flash button. 30 | 31 | ![Etcher](https://pbs.twimg.com/media/C29Ex0WXUAERiXw.jpg) 32 | 33 | > If you don't have an SD-card slot in your laptop then we will provide an external USB device. 34 | 35 | ### 1.2 Don't boot yet 36 | 37 | Before booting up the Pi we need to make some changes on the `boot` partition to reduce the RAM allocated to the GPU and to enable the Pi to work over a USB cable rather than with a WiFi or Ethernet adapter. 38 | 39 | Eject the SD card and re-insert if the `boot` partition is not visible. 40 | 41 | **GPU RAM split** 42 | 43 | Edit `config.txt` in the boot partition. On a Mac this will be mounted under `/Volumes/boot`. On Windows this will show up as a drive called `boot`. 44 | 45 | Add these two lines at the end of the file: 46 | 47 | ``` 48 | dtoverlay=dwc2 49 | gpu_mem=16 50 | ``` 51 | 52 | **Enable OTG** 53 | 54 | Edit the `cmdline.txt` file. 55 | 56 | **Make sure everything is kept on one line, do not split the line or it will break the system.** 57 | 58 | * Find the part of the line that says `rootwait`. 59 | * Add a (space) after `rootwait` then the following: `modules-load=dwc2,g_ether` 60 | 61 | **Enable SSH** 62 | 63 | The Raspberry Pi foundation took the decision to disable SSH on their Operating System by default, but this can be overridden by creating a file with any contents in the boot partition called `ssh` with no extension. 64 | 65 | If you're on a Mac, type in `touch /Volumes/boot/ssh` to create the file. On Windows make sure that you don't end up creating a file called ssh.txt (with an extension). 66 | 67 | ### 1.3 Plug in and boot up 68 | 69 | * Now you can eject your SD card from your laptop. 70 | * Before powering, slot the Blinkt onto the 40-pin header making sure its curved edges match those of the Pi 71 | * Plug the SD card into your Pi 72 | * The USB cable goes into the port labelled "usb" on the Pi Zero and then the other end goes into your laptop. 73 | 74 | *Do not plug the cable into the power socket* 75 | 76 | ![Raspberry Pi Zero](https://pbs.twimg.com/media/C3e_27aWQAELXDd.jpg) 77 | 78 | 79 | The activity LED will start flashing showing the Pi is booting - this could take up to 90s for the initial boot. 80 | 81 | You will be able to connect to the Pi with SSH through the hostname `raspberrypi.local` 82 | 83 | * Username: pi 84 | * Password: raspberry 85 | 86 | ``` 87 | $ ssh pi@raspberrypi.local 88 | ``` 89 | 90 | *If you're on Windows use Git bash to get access to the `ssh` command.* 91 | 92 | You won't have any internet access yet, so we will need to enable internet connection sharing on your laptop. 93 | 94 | > If you are working behind a restrictive or corporate network you will need to consult the [proxy guide](https://github.com/alexellis/docker-blinkt-workshop/blob/master/PROXIES.md). Skip section 1.4. 95 | 96 | > Pro tip: if you don't like typing passwords in type in `ssh-keygen` (accepting defaults) followed by `ssh-copy-id pi@raspberrypi.local` 97 | 98 | ### 1.4 Enable Internet Connection Sharing (ICS) 99 | 100 | On a Mac open the "Preferences" App then click "Sharing" followed by "Share the connection" from "WiFi" to "RNDIS/Ethernet Gadget". 101 | 102 | ![](http://blog.alexellis.io/content/images/2016/12/Screen-Shot-2016-12-20-at-8-48-43-PM.png) 103 | 104 | Restart the Raspberry Pi by typing in `sudo reboot` and try to connect again with `ssh` after about 30-60 seconds. You will find the IP address changes to something like `192.168.2.2` but the `raspberrypi.local` address will still work. 105 | 106 | If you're on Windows then open Control Panel and look for Network Connections and Internet Connection Sharing. 107 | 108 | > On Ubuntu Linux Internet Connection Sharing can be enabled through Network Manager, but if you can't enable sharing for whatever reason then installing a Squid Proxy server should enable you to get connected. 109 | 110 | If you've configured ICS correctly then you should be able to type in `ping -c 1 google.com` and get a response back. 111 | 112 | Depending on the output you may want to manually edit `/etc/resolv.conf` and enter in `nameserver 8.8.8.8` on a new line. This file can be overwritten automatically, so if that starts happening make it read-only with this command: `sudo chattr +i /etc/resolv.conf`. 113 | 114 | ### 1.5 Install Docker 115 | 116 | From your SSH session type in `curl -sSL https://get.docker.com | sudo -E sh`, this will setup Docker on your Pi. 117 | 118 | ![Docker installation](http://blog.alexellis.io/content/images/2016/12/Screen-Shot-2016-12-20-at-8-51-25-PM.png) 119 | 120 | Now let the Pi user have access to the Docker daemon and reboot by typing in: 121 | 122 | ``` 123 | $ sudo usermod -aG docker pi 124 | $ sudo reboot 125 | ``` 126 | 127 | To check everything worked you can list the running containers (which should come back empty) 128 | 129 | ``` 130 | $ docker ps 131 | ``` 132 | 133 | **Useful commands for containers and images:** 134 | 135 | |Command | Description | 136 | -------------------|--------------- 137 | | `docker images` | show the images in our library | 138 | | `docker search` | find an image on the public Hub | 139 | | `docker run -ti` | start running a container with a keyboard attached| 140 | | `docker run -d` | start running a container in the background (detached)| 141 | | `docker kill` | stop a running container | 142 | | `docker rm -f` | stop and remove a container | 143 | 144 | 145 | **Useful diagnostics:** 146 | 147 | |Command | Description | 148 | -------------------|--------------- 149 | | `docker stats` | show the RAM/CPU/network I/O usage of running containers | 150 | | `docker version` | essential for when reporting bugs, shows the Docker client/version and system architecture | 151 | | `docker info` | a deep diagnostics page for the Docker engine | 152 | -------------------------------------------------------------------------------- /2-BUILD.md: -------------------------------------------------------------------------------- 1 | ## 2 Build 2 | 3 | ### 2.1 - Base images for ARM devices. 4 | 5 | The two base images we will use for the workshop are: 6 | 7 | * `resin/rpi-raspbian:jessie` 8 | 9 | This image is produced by the [resin.io](https://resin.io) team who created a managed solution around IoT devices. It is ideal for Raspberry Pis because it is a very minimal version of Raspbian. 10 | 11 | You can use commands you may already be used to such as `apt-get install` and `dpkg`. 12 | 13 | > Docker base images tend to lack the common utilities we're used to such as `git`, `unzip` and even `man` pages. 14 | 15 | * `armhf/alpine:latest` 16 | 17 | The Alpine Linux images became popularized about 18 months ago, this Linux distribution builds against a C library called Musl instead of the standard GNU GLibc library meaning that it can fit a complete container OS inside a 2MB download. 18 | 19 | The `armhf` organization on the Docker Hub is maintained by Tianon, a contributor to the Docker project and the official set of images. He produces the `armhf` images through a Continuous Integration (CI) pipeline. 20 | 21 | **Auto-building Docker images** 22 | 23 | Dockerfiles for regular hardware such as that found in laptops and on the cloud can be built through an automated process in the Docker Hub. Unfortunately there is no support for auto-building Docker ARM images. For this reason be very cautious of running images from untrusted sources. 24 | 25 | **Pull the base images** 26 | 27 | Please start pulling the base images down from the Docker Hub, this could take a few minutes. 28 | 29 | ``` 30 | $ docker pull resin/rpi-raspbian:jessie 31 | $ docker pull armhf/alpine:latest 32 | ``` 33 | 34 | ### 2.1b 35 | 36 | If you're unfamiliar with working with a Linux system or how to code on a Raspberry Pi please read this guide: 37 | 38 | * [Coding on a Raspberry Pi](https://github.com/alexellis/docker-blinkt-workshop/blob/master/CODING.md) 39 | 40 | This helps explain: 41 | 42 | * working a text editor on the Pi 43 | * synchronizing files 44 | 45 | ### 2.2 - Running your first ARM container 46 | 47 | Now let's start a container with an interactive shell: 48 | 49 | ``` 50 | $ docker run -p 80:80 --name web -ti armhf/alpine:latest sh 51 | ``` 52 | 53 | If you're running behind a proxy then make sure you head over to the [proxy setup guide](https://github.com/alexellis/docker-blinkt-workshop/blob/master/PROXIES.md): 54 | 55 | ``` 56 | $ docker run -e http_proxy=$http_proxy -e https_proxy=$https_proxy \ 57 | -p 80:80 --name web -ti armhf/alpine:3.5 sh 58 | ``` 59 | 60 | * The `-e` parameter to `docker run` is used to make an environmental variable available inside the container. This is useful way of updating how a program works without changing code. 61 | 62 | 63 | Now install and start `nginx`: 64 | 65 | ``` 66 | / # mkdir -p /run/nginx/ 67 | 68 | / # apk add --update nginx 69 | 70 | / # nginx 71 | / # 72 | ``` 73 | 74 | We've just started an ARM container with Alpine Linux, the `apk add` instruction added a tiny web-server and when we ran the container we passed in `-p` which meant we opened up a port for it. 75 | 76 | * The `-ti` command meant we created an interactive connection to the container, where we can type input and read output 77 | * The final part of the command `sh` gives us a shell to type in commands, but this could be anything such as `ls` or `cat /etc/hostname` 78 | 79 | Go to your laptop and open http://raspberrypi.local in a web browser or `no_proxy="raspberrypi.local" curl -4 http://raspberrypi.local` from a Terminal. 80 | 81 | You should now see something like: 82 | 83 | ``` 84 | 404 not found 85 | 86 | Nginx 87 | ``` 88 | 89 | That message is OK because we've not added any config for a website yet. If you see the text nginx on the page/response then the container is working. 90 | 91 | On the Raspberry Pi type in `exit` to close the shell to the container and exit. 92 | 93 | ``` 94 | / # exit 95 | ``` 96 | 97 | We should now remove the stopped `web` container. Type in: 98 | 99 | ``` 100 | docker rm web 101 | ``` 102 | 103 | If the container is still running, then you can use this shorthand (which does a kill then remove): 104 | 105 | ``` 106 | docker rm -f web 107 | ``` 108 | 109 | ### 2.3 Build an ARM Dockerfile 110 | 111 | So now let's create a Dockerfile to do all of the above, so that we don't have to type in commands manually. 112 | 113 | > You can create a folder with the `mkdir folder_name` command and then use `cd folder_name` to enter into it. 114 | 115 | Create a config file for a virtual website under the name `raspberrypi.conf`: 116 | 117 | ``` 118 | server { 119 | server_name _; 120 | 121 | listen 80 default_server; 122 | listen [::]:80 default_server; 123 | 124 | root /var/www/html; 125 | index index.html index.htm index.nginx-debian.html; 126 | 127 | location / { 128 | try_files $uri $uri/ =404; 129 | } 130 | } 131 | ``` 132 | **raspberrypi.conf** 133 | 134 | Now let's build a Dockerfile to automate what we did in the previous step: 135 | 136 | ``` 137 | FROM armhf/alpine:3.5 138 | RUN apk add --update nginx 139 | RUN mkdir -p /run/nginx/ 140 | RUN rm /etc/nginx/conf.d/default.conf 141 | RUN mkdir -p /var/www/html/ 142 | RUN echo "Welcome to your Raspberry Pi" | tee -a /var/www/html/index.html 143 | 144 | COPY raspberrypi.conf /etc/nginx/conf.d/ 145 | 146 | EXPOSE 80 147 | CMD ["nginx", "-g", "daemon off;"] 148 | ``` 149 | *Dockerfile* 150 | 151 | **Explaining the Dockerfile** 152 | 153 | A Dockerfile means we get a repeatable image each time we perform our build. We can share Dockerfiles with other people and normally check them into our source control management system. This means that anyone can build a project with a Dockerfile without having to install any other dependencies. 154 | 155 | There are a few differences between using a Dockerfile and running steps in a shell through a container: 156 | 157 | * We picked a base image to start off from, any changes we need are laid over the top of this base 158 | * We added several `RUN` instructions which correspond to something we would type into a bash prompt 159 | * We copied in `raspberrypi.conf` from our host into the image so there would be a default website 160 | 161 | Finally: 162 | * We added a `CMD` instruction that will by invoked immediately when we type in `docker run` 163 | 164 | Build the image like this if you're not using a proxy: 165 | ``` 166 | $ docker build -t pi-nginx . 167 | ``` 168 | 169 | If you're behind a proxy build the container like this: 170 | 171 | ``` 172 | $ docker build --build-arg http_proxy=$http_proxy --build-arg https_proxy=$https_proxy \ 173 | -t pi-nginx . 174 | ``` 175 | 176 | Now test out the image: 177 | 178 | ``` 179 | $ docker run -p 80:80 -d --name web pi-nginx 180 | $ docker ps 181 | ``` 182 | 183 | In the example `-p 80:80` uses a static port mapping from the container to the host on port 80. Docker allows us to change the port on the host. That means we can run more than one instance of the container. For example we could bind to port 8080 on the host: 184 | 185 | ``` 186 | $ docker run -p 8080:80 -d pi-nginx 187 | ``` 188 | 189 | For scalability we sometimes want to run on any free port with the `-P` flag. After creating a container with the `-P` flag `docker port` or `docker ps` will show you which host port was used. 190 | 191 | Here's an example: 192 | 193 | ``` 194 | $ docker run -P -d pi-nginx 195 | 7490d74f8bb963727d3b4e6868dd052d89a08da0e1399017f61c212df5043373 196 | 197 | $ docker port 7490d 198 | 80/tcp -> 0.0.0.0:32768 199 | 200 | 201 | $ docker run -P -d pi-nginx 202 | 882f86c8e213b463c6970d190f412a26701fd595c5b2ad348b291004d31e8643 203 | 204 | $ docker port 882f 205 | 80/tcp -> 0.0.0.0:32769 206 | ``` 207 | 208 | We can type in the full URL into a web-browser: http://raspberrypi.local:32768 for example. 209 | 210 | The following short-cut will remove all the containers in our system leaving the images behind. If a container is still running it will be killed first because of the `-f` flag: 211 | 212 | ``` 213 | $ docker ps -aq | xargs docker rm -f 214 | ``` 215 | 216 | ### 2.3b Optimizing a Dockerfile 217 | 218 | A new Docker container is used to build each step in a Dockerfile, you will see these temporary containers in the output from `docker build` shortly. This can be slow on the Pi Zero, so ideally we want to reduce the amount of instructions by using fewer `RUN` commands and combining the instructions. 219 | 220 | Here is an optimized version of the Dockerfile: 221 | 222 | ``` 223 | FROM armhf/alpine:latest 224 | RUN apk add --update nginx && \ 225 | mkdir -p /run/nginx/ && \ 226 | rm /etc/nginx/conf.d/default.conf && \ 227 | mkdir -p /var/www/html/ && \ 228 | echo "Welcome to your Raspberry Pi" | tee -a /var/www/html/index.html 229 | 230 | COPY raspberrypi.conf /etc/nginx/conf.d/ 231 | 232 | EXPOSE 80 233 | CMD ["nginx", "-g", "daemon off;"] 234 | ``` 235 | 236 | Another side-effect of fewer lines in a Dockerfile is that it can take up less space on disk and be quicker to share between environments. 237 | 238 | ### 2.4 - Get the Blinkt library from Github 239 | 240 | The Raspbian Lite image is perfect for a headless server, especially when compared to the Pixel edition which is packed pull of applications and UI utilities. This does mean we need to install essential tools like `git`. 241 | 242 | Install `git` on the Pi: 243 | 244 | ``` 245 | $ sudo -E apt-get update && \ 246 | sudo -E apt-get install -qy git 247 | ``` 248 | 249 | **Clone the Blinkt! library** 250 | 251 | ``` 252 | $ git clone http://github.com/pimoroni/blinkt 253 | ``` 254 | 255 | **Build a Blinkt! Docker image** 256 | 257 | You can explore the code, or start building a Blinkt! Docker image right way. 258 | 259 | ``` 260 | $ cd blinkt 261 | $ docker build -t blinkt . 262 | ``` 263 | 264 | > If you're using a proxy remember to pass `--build-arg` as demonstrated in 2.3. 265 | 266 | This could take up to 5 minutes if you have already downloaded the base image. I measured 7m41 including a download of Resin's image from the Docker Hub. 267 | 268 | **Layer caching** 269 | 270 | Fortunately Docker has a very effective caching mechanism and if you issued another `docker build` without making further changes then this would only take a few seconds. Each line in the Dockerfile (generally) is split up into separate layers and each of those can be cached separately. I measured 1.4s on the second build. 271 | 272 | > For the Pi it's especially important to keep the changing parts such as the application source-code near the end of the file so that only the affected parts need to be re-built each time you change something. 273 | 274 | In the meantime spend 5-10 minutes checking out some of the Python examples provided in the repository: 275 | 276 | Some of the examples require additional libraries and alterations to the Dockerfiles, so stick to the ones which run out of the box. 277 | 278 | * [Blinkt! examples](https://github.com/pimoroni/blinkt/tree/master/examples) 279 | 280 | **Question:** 281 | 282 | Which example does the Dockerfile invoke by default? 283 | -------------------------------------------------------------------------------- /3-BLINKT.md: -------------------------------------------------------------------------------- 1 | # 3 Blinkt 2 | 3 | ### 3.1 - Knight Rider 4 | 5 | **System privileges** 6 | 7 | Docker runs containers by default in a sandbox with reduced system capabilities. For GPIO or Physical Computing we need full system privileges. This can be overridden by passing `--privileged` to Docker run. 8 | 9 | ``` 10 | $ docker run --privileged -ti blinkt 11 | 12 | ^CTraceback (most recent call last): 13 | File "larson.py", line 28, in 14 | time.sleep(0.1) 15 | KeyboardInterrupt 16 | ``` 17 | 18 | Now hit Control+C, edit the Dockerfile to run a different example, re-build and run it again. 19 | 20 | **Larson Scanner** 21 | 22 | The larson.py example is inspired by a futuristic car from an 80s TV show: 23 | 24 | * [Knight Rider KITT](https://en.wikipedia.org/wiki/KITT#KITT_.28Knight_Industries_Two_Thousand.29) 25 | 26 | The code uses a Sine wave to produce a looping red LED pulse from left to right. 27 | 28 | ### 3.2 - Controlling the LEDs 29 | 30 | We'll now create our own example and rebuild the Dockerfile to run it instead of the Larson Scanner. 31 | 32 | * Make a new directory for this example with `mkdir folder_name` then type in `cd folder_name`. 33 | 34 | * Create a new example file named pixel.py and copy in the lines below: 35 | 36 | The following program will set the first LED to red for 1 second and then turn it off again. 37 | 38 | If this file is run from the command-line i.e. `./pixel.py` the below tells our shell how to intepret the file. It's not necessary to add this but it is best practice. 39 | 40 | ``` 41 | #!/usr/bin/env python 42 | ``` 43 | 44 | Now import the blinkt library and the `time` library so we can inject a pause 45 | 46 | ``` 47 | from blinkt import set_clear_on_exit, set_pixel, show 48 | import time 49 | ``` 50 | 51 | Now let's make sure the Blinkt turns off if we hit Control + C. 52 | 53 | ``` 54 | set_clear_on_exit() 55 | ``` 56 | 57 | Now set a pixel to a colour such as red and call `show` so that the LEDs reflect the change: 58 | 59 | ``` 60 | set_pixel(0, 255, 0, 0) 61 | show() 62 | time.sleep(1) # 1 = 1 second 63 | ``` 64 | 65 | Here is the whole file for pixel.py: 66 | 67 | ``` 68 | #!/usr/bin/env python 69 | 70 | from blinkt import set_clear_on_exit, set_pixel, show 71 | import time 72 | 73 | set_clear_on_exit() 74 | 75 | set_pixel(0, 255, 0, 0) 76 | show() 77 | time.sleep(1) # 1 = 1 second 78 | ``` 79 | *pixel.py* 80 | 81 | **Create the Dockerfile** 82 | 83 | Because we already have a base image called `blinkt` with everything we need, we can use that as a base to build a new image with our `pixel.py` program: 84 | 85 | ``` 86 | FROM blinkt 87 | 88 | WORKDIR /root/ 89 | COPY pixel.py . 90 | 91 | CMD ["python", "pixel.py"] 92 | ``` 93 | 94 | Now build and run the new image: 95 | ``` 96 | $ docker build -t pixel . 97 | $ docker run --privileged -ti pixel 98 | ``` 99 | 100 | The first LED will turn red for one second and then turn off. You could try changing the colour before moving onto the next step. 101 | 102 | **Blinkt docs** 103 | 104 | Checkout the [Blinkt Docs](http://docs.pimoroni.com/blinkt/) for the ordering of parameters and to see how to make different coloured combinations. 105 | 106 | This is the signature for `set_pixel`: 107 | 108 | ``` 109 | set_pixel(led_number, red, green, blue) 110 | ``` 111 | 112 | The led_number should be between 0 and 7. 113 | 114 | 115 | ### 3.3 Light up all LEDs 116 | 117 | Now see if you can write a loop to light up all of the LEDs with a 0.5 second delay between each. 118 | 119 | A for loop in python takes this format: 120 | 121 | ``` 122 | for x in range(0, 8): 123 | print("This is value " + str(x)) 124 | 125 | This is value 0 126 | This is value 1 127 | This is value 2 128 | This is value 3 129 | This is value 4 130 | This is value 5 131 | This is value 6 132 | This is value 7 133 | ``` 134 | 135 | ### 3.4 Live coding 136 | 137 | Sometimes the workflow of `docker build` / `docker run` is quick enough for us to be productive. There is an alternative which allows us to iterate much more quickly without having to rebuild our container after each change. 138 | 139 | **Task: make a rainbow** 140 | 141 | We want to create a loop in our Python application to cycle through the colours of red, green and blue. 142 | 143 | * Loop over each of the colours (R,G,B) 144 | * Loop over each of the LEDs 145 | * set the pixel colour 146 | * update the display 147 | * wait for 0.5 seconds 148 | 149 | Even as a seasoned programmer it is still possible to make mistakes and to get typos, so we'll work with a "live coding" environment. Live coding can help us make rapid changes without rebuilding a Docker image. 150 | 151 | Create a new folder for this lab with `mkdir` and `cd` into it. 152 | 153 | The `pwd` command shows us the current path on a Linux shell. We will use this when running the live container: 154 | 155 | ``` 156 | $ pwd 157 | /home/pi/dockercon-blinkt/labs/3.4 158 | ``` 159 | 160 | Now we can share that folder into our Blinkt container with a (bind mount) and the `-v` flag: 161 | 162 | ``` 163 | docker run --privileged -v `pwd`:/root/examples -ti blinkt bash 164 | ``` 165 | 166 | **Notes on live coding:** 167 | 168 | * On the Raspberry Pi our code will be found in the /root/ directory ready for us to edit 169 | * We won't have any editor in the container, but we can use one on the Raspberry Pi (host) and our changes will reflect 170 | * Open a second `ssh` session to the Raspberry Pi to edit the code 171 | 172 | This is the answer to the coding task, it uses a Python feature called *tuples*. 173 | 174 | ``` 175 | from blinkt import set_clear_on_exit, set_pixel, show 176 | import time 177 | 178 | set_clear_on_exit() 179 | 180 | red = (255,0,0) 181 | green = (0,255,0) 182 | blue = (0,0,255) 183 | colors = [red,green,blue] 184 | 185 | for color in colors: 186 | for i in range(0, 8): 187 | set_pixel(i, color[0], color[1], color[2]) 188 | show() 189 | time.sleep(0.5) 190 | ``` 191 | 192 | **Task: loop forever** 193 | 194 | Can you make it loop forever or until Control + C is hit? 195 | 196 | **Task: create a Dockerfile for your code** 197 | 198 | Once you have finished your live coding session you may have created new files, make sure any changes you've made are reflected in your Dockerfile. Build a Docker image with your changes. 199 | 200 | -------------------------------------------------------------------------------- /4-WEB.md: -------------------------------------------------------------------------------- 1 | ## 4 Web applications 2 | 3 | Now we can control our Blinkt from the command-line or put it into an infinite loop, let's move on and create a web application. 4 | 5 | ### 4.1 Python Flask 6 | 7 | Python Flask is a framework that lets you turn your application into a web-server with very little effort. We will use it for the next task. 8 | 9 | **Task: Build a temperature server** 10 | 11 | **Flask and `pip`** 12 | 13 | The easiest way to install Python dependencies and packages is to use `pip`, `pip` can also be used to install `docker-compose` at a later stage. 14 | 15 | We won't install these on our Raspberry Pi, but we will add them to our Dockerfile: 16 | 17 | ``` 18 | FROM blinkt 19 | 20 | RUN apt-get update -qq && \ 21 | apt-get install -qy python-pip && \ 22 | pip install flask 23 | ``` 24 | 25 | > These steps will take a few minutes when we build our image below, but they will be cached into one layer. 26 | 27 | Now create a minimal Flask webserver to return the system temperature as a JSON value: 28 | 29 | The temperature is stored in (milli-degrees) Celsius at `/sys/class/thermal/thermal_zone0/temp`, on the Raspberry Pi you can use the `cat` command to view the current temperature. 30 | 31 | *server.py* 32 | 33 | ``` 34 | from flask import Flask, request, render_template 35 | import json 36 | app = Flask(__name__) 37 | 38 | @app.route('/', methods=['GET']) 39 | def home(): 40 | file = open("/sys/class/thermal/thermal_zone0/temp") 41 | data = file.read().rstrip() # remove trailing '\n' newline character. 42 | file.close() 43 | payload = json.dumps({ "temperature": data }) 44 | return payload 45 | 46 | if __name__ == '__main__': 47 | 48 | app.run(debug=False, host='0.0.0.0') 49 | ``` 50 | 51 | **Set the network port** 52 | 53 | A Flask server listens on port 5000 by default, so let's finish our Dockerfile. 54 | 55 | * Add the server.py file to the image 56 | * And include the `EXPOSE 5000` command so that Docker is aware of which ports will be needed 57 | 58 | ``` 59 | FROM blinkt 60 | RUN apt-get update -qy && \ 61 | apt-get install -qy python-pip && \ 62 | pip install flask 63 | 64 | WORKDIR /root/ 65 | COPY server.py . 66 | EXPOSE 5000 67 | 68 | CMD ["python", "server.py"] 69 | ``` 70 | 71 | Build your server and try it out: 72 | 73 | ``` 74 | $ docker build -t tempserver . 75 | $ docker run --name web -p 5000:5000 -d tempserver 76 | ``` 77 | 78 | * The `-p` flag makes sure Docker exposes port 5000 on our Raspberry Pi 79 | * `-d` means the code runs in the background (this means we can't use Control + C) 80 | 81 | Now try your website with http://raspberrypi.local:5000/ or with `curl`: 82 | 83 | ``` 84 | $ curl http://raspberrypi.local:5000 85 | {"temperature": "42774"} 86 | ``` 87 | 88 | Kill the webserver with `docker rm -f web` 89 | 90 | **Task: return a formatted temperature** 91 | 92 | * Now update the Python code so that the milli-degrees temperature is returned in Celsius i.e. (42.0). 93 | * Include Fahrenheit in the output. The [conversion from Celsius to Fahrenheit](http://www.rapidtables.com/convert/temperature/celsius-to-fahrenheit.htm) is `(Celsius * 1.8 + 32)`. 94 | 95 | Example response: 96 | 97 | ``` 98 | {"temperatureFahrenheit": "107.6", "temperatureCelsius": "42.0"} 99 | ``` 100 | 101 | String formatting can format a number to a set number of decimal places i.e 4 with `"{:.4f}"` or two with `"{:.2f}"` but it does not apply rounding. 102 | 103 | Here's an example of formatting a numeric value that will help with the task: `"{:.1f}".format(0.5166784)` 104 | 105 | Rounding can be done through the [round](https://docs.python.org/2/library/functions.html#round) function: `round(number, places)`. 106 | 107 | Python has a built-in read–eval–print loop REPL which can be used to test out Python code before integrating it into an application. Start up the image we built and force it to load the REPL like this: 108 | 109 | ``` 110 | $ docker run -ti tempserver /usr/bin/python 111 | >>> "{:.4f}".format(0.5166784) 112 | >>> round(0.5166784, 2) 113 | ``` 114 | 115 | When you're finished hit Control + D or type in `quit()`. 116 | 117 | Now update the code to return the example response above. You must run `docker build` before `docker run` after every change. 118 | 119 | **Task: deploy a Docker Swarm service** 120 | 121 | You can run a single-node Docker Swarm (cluster) with your Raspberry Pi. Unfortunately you won't be able to create a Swarm between your Pi and your laptop unless you are using Wi-Fi or Ethernet connected via USB. 122 | 123 | > The new PiZeroW has built-in WiFi and costs around twice the price of the original PiZero, this frees up the USB port for an external hard drive or similar. 124 | 125 | Before creating a Swarm service we need to initialize Swarm Mode: 126 | 127 | ``` 128 | $ docker swarm init 129 | ``` 130 | 131 | Now create a service and let Docker deploy it: 132 | 133 | ``` 134 | $ docker service create --name web --replicas=1 --publish 5000:5000 tempserver 135 | ``` 136 | 137 | You may see [an error](https://github.com/docker/docker/issues/29205) similar to this: 138 | 139 | ``` 140 | unable to pin image tempserver to digest: errors: 141 | denied: requested access to the resource is denied 142 | unauthorized: authentication required 143 | ``` 144 | 145 | This is just a warning which translated means "we could not find your image on the internet, so we'll use your local image instead". 146 | 147 | As you can see the `docker service create` command is similar to `docker run`, but instead of running a one-shot container, a service will be defined. Docker will automatically restart the task if it exits with an error. 148 | 149 | Get a list of the services you've created: 150 | 151 | ``` 152 | $ docker service ls 153 | ``` 154 | 155 | Check on a specific service: 156 | 157 | ``` 158 | $ docker service ps web 159 | ``` 160 | 161 | Please remove the service before going onto the next step with: 162 | 163 | ``` 164 | $ docker service rm web 165 | ``` 166 | 167 | **Task: Scale a service** 168 | 169 | We will now create a web-server with Docker Swarm that prints out the name of the running container. This will demonstrate the built-in load balancing pattern (round-robin): 170 | 171 | ``` 172 | $ docker service create --name identity --publish 80:80 alexellis2/nginx-helloworld:armhf 173 | ``` 174 | 175 | Now use the curl command several times to see the container name: 176 | 177 | ``` 178 | $ curl -4 localhost:80 179 | c0ca78f3f58e 180 | 181 | $ curl -4 localhost:80 182 | c0ca78f3f58e 183 | ``` 184 | 185 | Scale the service to three replicas using `docker service scale identity=3` and run curl again: 186 | 187 | ``` 188 | $ curl -4 localhost:80 189 | c0ca78f3f58e 190 | 191 | $ curl -4 localhost:80 192 | 53b5667f8f34 193 | 194 | $ curl -4 localhost:80 195 | b45d73f0aa1b 196 | 197 | $ curl -4 localhost:80 198 | c0ca78f3f58e 199 | ``` 200 | 201 | The built-in load-balancing can reduce the need for an external load-balancer such as NGinx. 202 | 203 | ### 4.2 Build an IoT LED server 204 | 205 | Now we will allow people to trigger our LEDs over the internet by sending a POST request to our Flask server. 206 | 207 | Here is the outline of a Python application: 208 | 209 | * It receives a HTTP post to /set_color 210 | 211 | * Sets all LEDs to a certain color 212 | 213 | * Returns a JSON response with the color values 214 | 215 | ```python 216 | from flask import Flask, request, render_template 217 | import json 218 | 219 | from blinkt import set_clear_on_exit, set_pixel, show 220 | 221 | set_clear_on_exit() 222 | app = Flask(__name__) 223 | 224 | @app.route('/set_color', methods=['POST']) 225 | def set_color(): 226 | data = request.json 227 | red = data["red"] 228 | green = data["green"] 229 | blue = data["blue"] 230 | 231 | for led in range(0, 8): 232 | set_pixel(led, int(red), int(green), int(blue)) 233 | show() 234 | 235 | return json.dumps({"status": "OK", "r": red, "g": green, "b": blue }) 236 | 237 | if __name__ == '__main__': 238 | app.run(debug=False, host='0.0.0.0') 239 | ``` 240 | 241 | We can re-use the same Dockerfile from the previous example: 242 | 243 | ``` 244 | FROM blinkt 245 | RUN apt-get update -qy && \ 246 | apt-get install -qy python-pip && \ 247 | pip install flask 248 | 249 | WORKDIR /root/ 250 | COPY server.py . 251 | EXPOSE 5000 252 | 253 | CMD ["python", "./server.py"] 254 | ``` 255 | 256 | **Task: Build and run the image** 257 | 258 | ``` 259 | $ docker build -t ledserver . 260 | $ docker run --privileged --name web -p 5000:5000 -d ledserver 261 | ``` 262 | 263 | To access the LED server either install the Google Chrome extension (Postman) or use `curl`: 264 | 265 | *Turn the LEDs red* 266 | 267 | ``` 268 | $ curl -H "Content-type: application/json" -d '{"red": 10, "green":0, "blue":0 }' raspberrypi.local:5000/set_color 269 | 270 | {"status": "OK", "r": 10, "b": 0, "g": 0} 271 | ``` 272 | 273 | *Turn the LEDs off* 274 | 275 | ``` 276 | $ curl -H "Content-type: application/json" -d '{"red": 0, "green":0, "blue":0 }' raspberrypi.local:5000/set_color 277 | 278 | {"status": "OK", "r": 0, "b": 0, "g": 0} 279 | ``` 280 | 281 | Unfortunately you cannot create a Docker Swarm service for this application because it needs to run in `--privileged` mode. 282 | 283 | **Task: Hack the program to change the color of individual LEDs** 284 | 285 | To maintain backwards compatibility add a new route for `/set_colors` 286 | 287 | The JSON request to change an individual LED should be an array with 8 sets of RGB values: 288 | 289 | ``` 290 | [ {"red": 0, "green": 0, "blue": 0}, 291 | {"red": 0, "green": 0, "blue": 0}, 292 | {"red": 0, "green": 0, "blue": 0}, 293 | {"red": 0, "green": 0, "blue": 0}, 294 | {"red": 0, "green": 0, "blue": 0}, 295 | {"red": 0, "green": 0, "blue": 0}, 296 | {"red": 0, "green": 0, "blue": 0}, 297 | {"red": 0, "green": 0, "blue": 0}] 298 | ``` 299 | 300 | When you've updated the code build and run the new image: 301 | 302 | ``` 303 | $ docker build --build-arg http_proxy=$http_proxy --build-arg https_proxy=$https_proxy \ 304 | -t setcolors . 305 | $ docker run --privileged --name setcolors -d -p 5000:5000 setcolors:latest 306 | ``` 307 | 308 | Now use `curl` to set each LED to different colors: 309 | 310 | ``` 311 | $ export no_proxy="raspberrypi.local" 312 | $ curl http://raspberrypi.local:5000/set_colors -H "Content-type: application/json" -d ' 313 | [{"red": 100, "green": 0, "blue": 0}, 314 | {"red": 0, "green": 100, "blue": 0}, 315 | {"red": 0, "green": 0, "blue": 100}, 316 | {"red": 0, "green": 100, "blue": 0}, 317 | {"red": 100, "green": 0, "blue": 0}, 318 | {"red": 0, "green": 100, "blue": 0}, 319 | {"red": 0, "green": 0, "blue": 100}, 320 | {"red": 0, "green": 100, "blue": 0} 321 | ]' 322 | ``` 323 | 324 | When you've tried a few different combinations send an "off" signal, then kill the container: 325 | 326 | ``` 327 | $ curl -H "Content-type: application/json" -d '{"red": 0, "green":0, "blue":0 }' raspberrypi.local:5000/set_color 328 | 329 | {"status": "OK", "r": 0, "b": 0, "g": 0} 330 | 331 | $ docker rm -f setcolors 332 | ``` 333 | 334 | ### 4.3 Rolling swarm updates 335 | 336 | Docker Swarm allows us to perform a rolling update to a live set of Swarm services. Let's see that in action. 337 | 338 | ``` 339 | $ docker service create --name progressbar \ 340 | --mount type=bind,source=/sys,destination=/sys \ 341 | --mode=global alexellis2/progress-blinkt:red 342 | ``` 343 | 344 | * `--name progressbar` - gives the service an identity we can use with `docker service rm/ps/inspect` 345 | * `--mount` - makes the Pi's GPIO pins accessible within Docker Swarm 346 | * `--mode=global` - makes sure we only ever run one of these tasks per host in the swarm 347 | 348 | We start off with a red progress bar animation from the `alexellis2/progress-blinkt:red` image. 349 | 350 | Now perform a rolling update with `docker service update`: 351 | 352 | `$ docker service update progressbar --image=alexellis2/progress-blinkt:blue` 353 | 354 | The Pi will download a 655 KB image and then kill the red container before switching over. 355 | 356 | If this was a production deployment and we found a bug in the image, we could type in `docker service update progressbar --rollback` 357 | 358 | There is also a green image if you would like to try that: `alexellis2/progress-blinkt:green` 359 | 360 | > For more reading checkout the tutorial: [Control GPIO with Docker Swarm 361 | ](http://blog.alexellis.io/gpio-on-swarm/) 362 | 363 | **Tiny Docker images** 364 | 365 | The smallest Docker images either contain a single static binary (like the progress-blinkt image) or they are based upon the Alpine Linux image which normally comes in at around 2-5MB. 366 | -------------------------------------------------------------------------------- /5-HACK.md: -------------------------------------------------------------------------------- 1 | ## 5 Hack 2 | 3 | In this final part of the workshop we turn things over to you to start hacking. A guide is provided as a starting-point: 4 | 5 | ### 5.1 Build a status monitor for an API or tool 6 | 7 | Now that we've created what is effectively an LED as a Service we can combine that with other information on the Internet to create a meaningful project. 8 | 9 | Here are some ideas: 10 | 11 | #### Hack on something completely different 12 | 13 | You can take the Pi Zero and Blinkt in your own direction, you may want to do this if you already have ideas or are used to programming with Python. 14 | 15 | #### 1. Github issue tracker - change colour depending on the number of issues on your repository 16 | 17 | Github libraries are available here: https://developer.github.com/libraries/ 18 | 19 | #### 2. Build monitor - query JenkinsCI to see if the Docker CI builds are working correctly. 20 | 21 | If you go down this route, don't try to run Jenkins on the Pi Zero, but on your laptop. 22 | 23 | * [Jenkins on Docker tutorial](http://blog.alexellis.io/jenkins-2-0-first-impressions/) 24 | 25 | * Python Gist [jobstatus.py](https://gist.github.com/alexellis/55677236693d82b3bdcdccccd23df8fb) 26 | 27 | #### 3. Create a web-page to control the Blinkt 28 | 29 | You could also create a web-page to control the Blinkt using Python Flask. 30 | 31 | Below is an example of a Flask site that plays Internet radio stations using jQuery and Flask view templates: 32 | * [alexellis/pyPlaylist](https://github.com/alexellis/pyPlaylist). 33 | 34 | * [Flask quickstart](http://flask.pocoo.org/docs/0.12/quickstart/) 35 | 36 | #### 4. Show the number of people in space as LEDs between 0 and 8 37 | 38 | We'll outline the code for this if you would prefer to follow the workshop track. 39 | 40 | **Show the number of people in space** 41 | 42 | Let's put together a skeleton application for counting astronauts. 43 | 44 | If you didn't get around to enhancing the LED server code in the previous example, then you can use the code in the Labs/4.3a folder which allows you to turn on individual LEDs. 45 | 46 | *Dockerfile* 47 | 48 | ``` 49 | FROM blinkt 50 | RUN apt-get update -qy && \ 51 | apt-get install -qy python-pip && \ 52 | pip install requests 53 | 54 | WORKDIR /root/ 55 | COPY app.py . 56 | EXPOSE 5000 57 | CMD ["python", "./app.py"] 58 | ``` 59 | 60 | We can drop the Flask installation here, but we will need the `requests` modules for HTTP/HTTPs calls to an API. 61 | 62 | *app.py* 63 | 64 | ``` 65 | import requests 66 | import json 67 | import time 68 | import os 69 | 70 | def get_amount(): 71 | output = requests.get("http://api.open-notify.org/astros.json") 72 | payload = output.json() 73 | return payload["number"] 74 | 75 | def post_colors(host, amount): 76 | body = {"red": 0, "green": 0,"blue": 0} 77 | if amount == 6: 78 | body["red"] = 255 79 | body["blue"] = 0 80 | body["green"] = 0 81 | 82 | output = requests.post(host+"/set_color", json=body) 83 | return output.status_code 84 | 85 | while(True): 86 | host = os.getenv("HOST_URL") 87 | amount = get_amount() 88 | status = post_colors(host, amount) 89 | print(str(status)) 90 | 91 | time.sleep(5) 92 | ``` 93 | 94 | Build and run the application: 95 | 96 | ``` 97 | $ docker build -t monitor . 98 | $ docker run -e HOST_URL=http://raspberrypi.local:5000 -ti monitor 99 | ``` 100 | 101 | > If raspberrypi.local gives you an error, then find the IP address of usb0 and use that instead, i.e. `ifconfig usb0` 102 | 103 | We have used an environmental variable to configure the URL for the Raspberry Pi's URL, this is useful especially if we have more than one Pi we need to address or if we change the hostname. To pass variables to docker at runtime use `-e key=value` and to pass them at build time use `--build-arg key=value`. 104 | 105 | **A note on rate-limiting** 106 | 107 | Before you start querying an API over the Internet it's always a good idea to check if rate limiting is enforced. This is where you are limited on how many requests you can make per second, per hour or per day. Sometimes registering for an official account with a service extends the amount of API hits you can request per day. 108 | 109 | * [Github rate limiting rules](https://developer.github.com/v3/#rate-limiting) 110 | -------------------------------------------------------------------------------- /CODING.md: -------------------------------------------------------------------------------- 1 | ### Coding on the Pi 2 | 3 | This document covers coding on the Pi. 4 | 5 | There are three simple ways you can synchronize code between your laptop or the Pi. 6 | 7 | ### Edit on the device 8 | 9 | Editing on the device is the simplest option if you are familiar with UNIX text-based editors. 10 | 11 | * Nano 12 | 13 | Beginners may use the pre-supplied `nano` editor to edit and create files. 14 | 15 | Usage: 16 | 17 | ``` 18 | $ nano filename.py 19 | ``` 20 | 21 | |Command | Description | 22 | -------------------|------------------------ 23 | | `Control + X` | Exit | 24 | | `Control + O` | write file to the disk | 25 | | `Control + K` | cut a line | 26 | | `Control + U` | paste a line | 27 | | `Control + W` | find in current file | 28 | 29 | > For a nano cheat-sheet checkout: http://www.tuxradar.com/content/text-editing-nano-made-easy 30 | 31 | * Cat 32 | 33 | If you need to drop a single text file onto the Pi you can use the `cat` command and a bash pipe like this: 34 | 35 | ``` 36 | $ cat > Dockerfile 37 | ``` 38 | 39 | (Now paste the contents in) 40 | 41 | ``` 42 | FROM armhf/alpine:latest 43 | CMD ["cat", "/etc/hostname"] 44 | ``` 45 | 46 | (Now it Control + D) 47 | 48 | You'll see your file created in the current directory. I use this technique a lot when working on remote systems. 49 | 50 | ### Use `sftp/scp` 51 | 52 | If you edit the code on your local computer then you can copy files up to the Raspberry Pi like this: 53 | 54 | ``` 55 | $ scp -r lab2_2 pi@raspberrypi.local:~/ 56 | ``` 57 | 58 | That will copy the `lab2_2` folder from your laptop to the home directory on the Pi. 59 | 60 | Copying from the Pi to the laptop is also useful: 61 | 62 | ``` 63 | $ scp -r pi@raspberrypi.local:~/lab2_2 . 64 | ``` 65 | 66 | ### Use `git` 67 | 68 | You can create a repository on Github and use `git push` and `git pull` to synchronize files. 69 | 70 | [Getting started with Git](https://git-scm.com/book/en/v1/Getting-Started) 71 | 72 | #### Advanced techniques 73 | 74 | You can mount a NFS or Samba filesystem from the Pi to your laptop or visa-versa. This is an advanced technique and will take some time to setup. 75 | -------------------------------------------------------------------------------- /CONCLUSION.md: -------------------------------------------------------------------------------- 1 | ## Workshop conclusion 2 | 3 | Thanks for taking part in this Raspberry Pi and Docker workshop. 4 | 5 | ### Continue learning Docker 6 | 7 | If you'd like to continue learning Docker for the Pi and maybe move on to the [Raspberry Pi Model 3](https://shop.pimoroni.com/products/raspberry-pi-3) (a more powerful version with quad-core processor, 1GB RAM and Ethernet) then check out Docker Captain Alex Ellis' blog series: 8 | 9 | * [Setting up a Pi Zero with Docker over a USB cable](http://blog.alexellis.io/docker-engine-in-your-pocket/) 10 | 11 | * [Gotchas on ARM/Pi boards](http://blog.alexellis.io/5-things-docker-rpi/) 12 | 13 | * [Raspberry Pi tag-series](http://blog.alexellis.io/tag/raspberry-pi/) 14 | 15 | ### Using the Pi at home 16 | 17 | After you get home you can continue using your Pi Zero attached to your laptop or a PC with the red OTG cable, or you can purchase a WiFi / Ethernet USB dongle. 18 | 19 | > If you're using the Pi Zero W (newest edition) you won't need to purchase an additoinal dongle. 20 | 21 | **Connectivity:** 22 | 23 | * [Three port hub and ethernet](https://shop.pimoroni.com/products/usb-multi-function-lan-adaptor) 24 | 25 | * [Official Raspberry Pi WiFi Dongle](https://shop.pimoroni.com/products/official-raspberry-pi-wifi-dongle) 26 | 27 | * [Raspberry Pi Zero Adaptor Kit](https://shop.pimoroni.com/products/zero-adaptor-kit) 28 | 29 | *An adapter cable is needed to plug-in USB-A style devices* 30 | 31 | **Power:** 32 | 33 | If you want to power the Pi up without using an OTG cable then a regular phone charger will be fine if it supplies 1A or greater. If in doubt you can also buy the official Pi adapter: 34 | 35 | * [Raspberry Pi Universal Power Supply](https://shop.pimoroni.com/products/raspberry-pi-universal-power-supply) 36 | 37 | **Stockists:** 38 | 39 | In the US the best options for purchasing accessories would be Adafruit and Microcenter, but Pimoroni also ships world-wide. 40 | 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Alex Ellis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PROXIES.md: -------------------------------------------------------------------------------- 1 | ## Working with proxies 2 | 3 | If you're on a restrictive network then it may be necessary to make use of a proxy for all of the Docker commands in the workshop. 4 | 5 | Certain WiFi networks will prevent you from enabling Internet Connection Sharing, this guide will also help work-around that restriction. 6 | 7 | ### 1.0 Set up a proxy server on your laptop 8 | 9 | Run the following Docker image: 10 | 11 | ``` 12 | $ docker run --name proxy -p 3128:3128 -d alexellis2/squid:latest 13 | ``` 14 | 15 | You can pick up the Dockerfile and rebuild from source from Github: [alexellis2/images/squid](https://github.com/alexellis/images/tree/master/squid). 16 | 17 | **Test connectivity** 18 | 19 | To test the connectivity run `curl -v -x http://address_of_laptop:3218 https://www.google.com` 20 | 21 | **Configure the Pi** 22 | 23 | Now on the Raspberry Pi edit ~/.bash_profile and add these two lines: 24 | 25 | ``` 26 | export http_proxy=http://address_of_laptop:3128 27 | export https_proxy=http://address_of_laptop:3218 28 | ``` 29 | 30 | Also run them on the shell or log out and in again. 31 | 32 | ### 1.1 `docker pull` 33 | 34 | To pull images from the Docker Hub you will need to update the systemd unit file on the Raspberry Pi with the IP address or name of your PC or Mac. 35 | 36 | Make the following folder: 37 | 38 | ``` 39 | # sudo mkdir -p /etc/systemd/system/docker.service.d/ 40 | ``` 41 | 42 | Now create the following file `/etc/systemd/system/docker.service.d/http-proxy.conf`: 43 | 44 | ``` 45 | [Service] 46 | Environment="HTTP_PROXY=http://address_of_laptop:3128" 47 | Environment="HTTPS_PROXY=http://address_of_laptop:3128" 48 | ``` 49 | 50 | Restart the Pi with `sudo reboot`. 51 | 52 | ### 1.2 `docker build` 53 | 54 | For the time being the best way to use a proxy with `docker build` is to pass environmental variables temporarily through `--build-arg`: 55 | 56 | ``` 57 | docker build --build-arg http_proxy=$http_proxy --build-arg https_proxy=$https_proxy -t myimage . 58 | ``` 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Docker and IoT 2 | 3 | **Get into physical computing with the Pimoroni Blinkt! add-on** 4 | 5 | * The Raspberry Pi is a low-energy microcomputer the size of a credit-card and has sold over 10 million units educating children and inspiring makers alike. 6 | 7 | * With the Pi, you'll be building, shipping and running Docker containers in no time and learning how to interact with physical hardware to create your own IoT devices. 8 | 9 | * You will also get a chance to play with Docker Swarm on your Pi to deploy a micro-service. And, you will take home a free Raspberry Pi Zero kit, including a super bright Pimoroni Blinkt! 8-LED RGB add-on. 10 | 11 | **Discount on hardware for this workshop** 12 | 13 | This workshop was originally designed for Dockercon 2017 in Austin. You can read the workshop notes and get a 10% discount off the hardware kit here: [Dockercon 2017 - Captain's Log](https://blog.alexellis.io/dockercon-2017-captains-log/). 14 | 15 | ### Pre-requisites 16 | 17 | * Install a text editor 18 | 19 | If you don't usually work with code then please install one of the following cross-platform text-editors: 20 | 21 | * [Visual Studio Code](https://code.visualstudio.com) 22 | * [Atom](https://atom.io) 23 | 24 | **For Windows users** 25 | 26 | * Install Git for Windows 27 | 28 | Please go ahead and install Git for Windows so that you have access to `ssh`, `scp`, `sftp` and a terminal: 29 | 30 | https://git-scm.com/downloads 31 | 32 | * Install Bonjour networking service 33 | 34 | The Raspberry Pi uses Apple's Bonjour/Avahi service to advertise its IP address. You will need to install Bonjour which is packaged in the following download. If you have iTunes installed you should already have the Bonjour service installed and started. 35 | 36 | * [Bonjour Print Services for Windows](https://support.apple.com/kb/DL999?locale=en_US) 37 | 38 | > If you struggle to setup Internet Connection Sharing because you're on Linux or a picky WiFi network then please follow the guide for configuring a [Proxy server](https://github.com/alexellis/docker-blinkt-workshop/blob/master/PROXIES.md). 39 | 40 | ### Workshop format 41 | 42 | This is a self-paced workshop designed to be followed in a class or individually. It starts with Part 1 which involves setting up the Pi and Blinkt LED board then moves onto building Docker images, programming the LEDs and creating up a cluster. 43 | 44 | The final part involves putting everything together to create an application. If you're taking part in a class you can do this last part individually or pair up. 45 | 46 | The hardware you need is described in [Part 1 - Setup](https://github.com/alexellis/docker-blinkt-workshop/blob/master/1-SETUP.md) and you can get a [10% discount from Pimoroni](https://blog.alexellis.io/dockercon-2017-captains-log/) if needed. 47 | 48 | **Class notes:** 49 | 50 | > We will have TAs on-hand to help out. Please let us know if you need help or if things aren't working right. 51 | 52 | ### [Part 1 - Setup](https://github.com/alexellis/docker-blinkt-workshop/blob/master/1-SETUP.md) 53 | 54 | Flash Raspbian, configure OTG networking and Internet Connection Sharing, then install Docker. 55 | 56 | ### [Part 2 - Build](https://github.com/alexellis/docker-blinkt-workshop/blob/master/2-BUILD.md) 57 | 58 | Run your first ARM Docker container, learn about base image differences and prepare to work with GPIO. 59 | 60 | ### [Part 3 - Blinkt](https://github.com/alexellis/docker-blinkt-workshop/blob/master/3-BLINKT.md) 61 | 62 | Get familiar with the Blinkt and start hacking 63 | 64 | ### [Part 4 - Web applications](https://github.com/alexellis/docker-blinkt-workshop/blob/master/4-WEB.md) 65 | 66 | Understand how to create a Web application in Python and an IoT LED server 67 | 68 | ### [Part 5 - Hack](https://github.com/alexellis/docker-blinkt-workshop/blob/master/5-HACK.md) 69 | 70 | Put everything you've learnt into practice on a larger application. 71 | 72 | ### [Conclusion](https://github.com/alexellis/docker-blinkt-workshop/blob/master/CONCLUSION.md) 73 | 74 | Find out how to use your Pi Zero at home and where to find more great content. 75 | 76 | **Get in touch** 77 | 78 | You can follow me on [Twitter @alexellisuk](https://twitter.com/alexellisuk) or read more about [Docker and Raspberry Pi on my blog](https://blog.alexellis.io/). 79 | -------------------------------------------------------------------------------- /STATIC_IP.md: -------------------------------------------------------------------------------- 1 | Static IP instructions 2 | ======================= 3 | 4 | This page gives instructions for setting up a static IP for if you're running into issues with IPv6 or Avahi. 5 | 6 | > Credit: zangzi from Dockercon US workshop. 7 | 8 | Pre-reqs: 9 | 10 | * You must have access to ext4 partitions through a utility or Linux laptop 11 | * A separate RPi could be used or live booting Linux OS and an SD card reader. 12 | 13 | Normally Windows / Mac OS do not have access to write to an ext4 filesystem. 14 | 15 | On the Raspberry Pi: 16 | 17 | Edit `/etc/network/interfaces`: 18 | 19 | Add this: 20 | 21 | ``` 22 | allow-hotplug usb0 23 | iface usb0 inet static 24 | address 10.0.13.2 25 | netmask 255.255.255.0 26 | network 10.0.13.0 27 | broadcast 10.0.13.255 28 | gateway 10.0.13.1 29 | dns-nameservers 8.8.8.8 8.8.4.4 30 | ``` 31 | 32 | On your Mac/Windows/Linux set a static IP address for the USB line: 33 | 34 | ``` 35 | address 10.0.13.1 36 | netmask 255.255.255.0 37 | network 10.0.13.0 38 | broadcast 10.0.13.255 39 | gateway 10.0.13.1 40 | ``` 41 | 42 | -------------------------------------------------------------------------------- /labs/3.2/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM blinkt 2 | COPY pixel.py . 3 | CMD ["python", "pixel.py"] 4 | -------------------------------------------------------------------------------- /labs/3.2/pixel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from blinkt import set_clear_on_exit, set_pixel, show 3 | 4 | import time 5 | 6 | set_clear_on_exit() 7 | set_pixel(0, 255, 0, 0) 8 | show() 9 | 10 | time.sleep(1) # 1 = 1 second 11 | -------------------------------------------------------------------------------- /labs/3.3/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM blinkt 2 | COPY pixel.py . 3 | CMD ["python", "pixel.py"] 4 | -------------------------------------------------------------------------------- /labs/3.3/pixel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from blinkt import set_clear_on_exit, set_pixel, show 3 | 4 | import time 5 | 6 | set_clear_on_exit() 7 | 8 | for i in range(0, 8): 9 | set_pixel(i, 0, 255, 0) 10 | show() 11 | 12 | time.sleep(1) # 1 = 1 second 13 | -------------------------------------------------------------------------------- /labs/3.4/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM blinkt 2 | COPY pixel.py . 3 | CMD ["python", "pixel.py"] 4 | -------------------------------------------------------------------------------- /labs/3.4/pixel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from blinkt import set_clear_on_exit, set_pixel, show 3 | 4 | import time 5 | 6 | set_clear_on_exit() 7 | 8 | red = (255,0,0) 9 | green = (0,255,0) 10 | blue = (0,0,255) 11 | colors = [red,green,blue] 12 | 13 | for color in colors: 14 | for i in range(0, 8): 15 | set_pixel(i, color[0], color[1], color[2]) 16 | show() 17 | time.sleep(0.5) 18 | -------------------------------------------------------------------------------- /labs/4.1/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM blinkt 2 | RUN apt-get update -qy && \ 3 | apt-get install -qy python-pip && \ 4 | pip install flask 5 | 6 | WORKDIR /root/ 7 | COPY server.py . 8 | EXPOSE 5000 9 | 10 | CMD ["python", "./server.py"] 11 | -------------------------------------------------------------------------------- /labs/4.1/server.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request, render_template 2 | import json 3 | app = Flask(__name__) 4 | 5 | @app.route('/', methods=['GET']) 6 | def home(): 7 | file = open("/sys/class/thermal/thermal_zone0/temp") 8 | data = file.read().rstrip() # remove trailing '\n' newline character. 9 | file.close() 10 | payload = json.dumps({ "temperature": data }) 11 | return payload 12 | 13 | if __name__ == '__main__': 14 | 15 | app.run(debug=False, host='0.0.0.0') 16 | -------------------------------------------------------------------------------- /labs/4.2/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM blinkt 2 | RUN apt-get update -qy && \ 3 | apt-get install -qy python-pip && \ 4 | pip install flask 5 | 6 | WORKDIR /root/ 7 | COPY server.py . 8 | EXPOSE 5000 9 | 10 | CMD ["python", "./server.py"] 11 | -------------------------------------------------------------------------------- /labs/4.2/server.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request, render_template 2 | import json 3 | 4 | from blinkt import set_clear_on_exit, set_pixel, show 5 | 6 | set_clear_on_exit() 7 | app = Flask(__name__) 8 | 9 | @app.route('/set_color', methods=['POST']) 10 | def set_color(): 11 | data = request.json 12 | red = data["red"] 13 | green = data["green"] 14 | blue = data["blue"] 15 | 16 | for led in range(0, 8): 17 | set_pixel(led, int(red), int(green), int(blue)) 18 | show() 19 | 20 | return json.dumps({"status": "OK", "r": red, "g": green, "b": blue }) 21 | 22 | if __name__ == '__main__': 23 | 24 | app.run(debug=False, host='0.0.0.0') 25 | -------------------------------------------------------------------------------- /labs/4.2a/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM blinkt 2 | RUN apt-get update -qy && \ 3 | apt-get install -qy python-pip && \ 4 | pip install flask 5 | 6 | WORKDIR /root/ 7 | COPY server.py . 8 | EXPOSE 5000 9 | 10 | CMD ["python", "./server.py"] 11 | -------------------------------------------------------------------------------- /labs/4.2a/server.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request, render_template 2 | import json 3 | 4 | from blinkt import set_clear_on_exit, set_pixel, show 5 | 6 | set_clear_on_exit() 7 | app = Flask(__name__) 8 | 9 | @app.route('/set_color', methods=['POST']) 10 | def set_color(): 11 | data = request.json 12 | red = data["red"] 13 | green = data["green"] 14 | blue = data["blue"] 15 | 16 | for led in range(0, 8): 17 | set_pixel(led, int(red), int(green), int(blue)) 18 | show() 19 | 20 | return json.dumps({"status": "OK", "r": red, "g": green, "b": blue }) 21 | 22 | 23 | @app.route('/set_colors', methods=['POST']) 24 | def set_colors(): 25 | data = request.json 26 | for led in range(0, 8): 27 | red,green,blue=0,0,0 28 | if led < len(data): 29 | red = data[led]["red"] 30 | green = data[led]["green"] 31 | blue = data[led]["blue"] 32 | set_pixel(led, int(red), int(green), int(blue)) 33 | show() 34 | return json.dumps({"status": "OK", "data": data }) 35 | 36 | 37 | if __name__ == '__main__': 38 | 39 | app.run(debug=False, host='0.0.0.0') 40 | -------------------------------------------------------------------------------- /labs/5.1/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM blinkt 2 | RUN apt-get update -qy && \ 3 | apt-get install -qy python-pip && \ 4 | pip install requests 5 | 6 | WORKDIR /root/ 7 | COPY app.py . 8 | EXPOSE 5000 9 | ENV HOST_URL http://raspberrypi.local:5000 10 | ENV no_proxy raspberrypi.local 11 | CMD ["python", "./app.py"] 12 | -------------------------------------------------------------------------------- /labs/5.1/app.py: -------------------------------------------------------------------------------- 1 | 2 | import requests 3 | import json 4 | import time 5 | import os 6 | 7 | def get_amount(): 8 | output = requests.get("http://api.open-notify.org/astros.json") 9 | payload = output.json() 10 | return payload["number"] 11 | 12 | def post_colors(host, amount): 13 | body = {"red": 0, "green": 0,"blue": 0} 14 | if amount == 6: 15 | body["red"] = 255 16 | body["blue"] = 0 17 | body["green"] = 0 18 | 19 | output = requests.post(host+"/set_color", json=body) 20 | return output.status_code 21 | 22 | while(True): 23 | host = os.getenv("HOST_URL") 24 | amount = get_amount() 25 | status = post_colors(host, amount) 26 | print(str(status)) 27 | 28 | time.sleep(5) 29 | --------------------------------------------------------------------------------