├── .gitignore ├── haproxy ├── Dockerfile ├── Dockerfile.template ├── run.sh └── haproxy.cfg ├── webcam ├── Dockerfile ├── Dockerfile.template └── run.py ├── octoprint ├── Dockerfile ├── bin │ ├── restart.sh │ ├── reboot.sh │ ├── streaming.sh │ ├── poweroff.sh │ └── run.sh ├── etc │ └── config.yaml └── Dockerfile.template ├── docker-compose-multi.sh ├── .env-distr ├── .env-extra-distr ├── .travis.yml ├── docker-compose.yml ├── scripts └── deploy.sh └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | -------------------------------------------------------------------------------- /haproxy/Dockerfile: -------------------------------------------------------------------------------- 1 | Dockerfile.template -------------------------------------------------------------------------------- /webcam/Dockerfile: -------------------------------------------------------------------------------- 1 | Dockerfile.template -------------------------------------------------------------------------------- /octoprint/Dockerfile: -------------------------------------------------------------------------------- 1 | Dockerfile.template -------------------------------------------------------------------------------- /octoprint/bin/restart.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Restart OctoPrint 3 | # We just kill OctoPrint, container will restart 4 | 5 | echo "*** Restarting OctoPrint" 6 | pkill octoprint 7 | -------------------------------------------------------------------------------- /octoprint/bin/reboot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Reboot host 3 | 4 | echo "*** Rebooting OctoPrint server" 5 | DBUS_SYSTEM_BUS_ADDRESS=unix:path=/host/run/dbus/system_bus_socket \ 6 | dbus-send \ 7 | --system \ 8 | --print-reply \ 9 | --dest=org.freedesktop.systemd1 \ 10 | /org/freedesktop/systemd1 \ 11 | org.freedesktop.systemd1.Manager.Reboot 12 | -------------------------------------------------------------------------------- /octoprint/bin/streaming.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Start / Stop the stream 3 | 4 | Command="$1" 5 | Url="http://webcam:5200" 6 | 7 | case "${Command}" in 8 | start) 9 | curl "${Url}/on" 10 | ;; 11 | stop) 12 | curl "${Url}/off" 13 | ;; 14 | *) 15 | echo "$0: Invalid action" 16 | exit 1 17 | ;; 18 | esac 19 | 20 | echo "" 21 | exit 0 22 | -------------------------------------------------------------------------------- /octoprint/bin/poweroff.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Power off host 3 | 4 | echo "*** Power off OctoPrint server" 5 | DBUS_SYSTEM_BUS_ADDRESS=unix:path=/host/run/dbus/system_bus_socket \ 6 | dbus-send \ 7 | --system \ 8 | --print-reply \ 9 | --dest=org.freedesktop.systemd1 \ 10 | /org/freedesktop/systemd1 \ 11 | org.freedesktop.systemd1.Manager.PowerOff 12 | -------------------------------------------------------------------------------- /octoprint/bin/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Check for certificates and start haproxy 3 | 4 | BaseDir=/opt/octoprint/data/octoprint 5 | 6 | if [ ! -f ${BaseDir}/config.yaml ] 7 | then 8 | echo "*** Copying initial config file" 9 | mkdir -p ${BaseDir} 10 | cp etc/config.yaml ${BaseDir} 11 | fi 12 | 13 | echo "*** Starting octoprint" 14 | exec /opt/octoprint/OctoPrint/venv/bin/octoprint --basedir ${BaseDir} serve --iknowwhatimdoing 15 | -------------------------------------------------------------------------------- /haproxy/Dockerfile.template: -------------------------------------------------------------------------------- 1 | # haproxy http/https proxy 2 | ARG OP_MACHINE_NAME 3 | 4 | FROM balenalib/${OP_MACHINE_NAME:-%%BALENA_MACHINE_NAME%%}-debian:buster 5 | 6 | RUN apt-get update && \ 7 | apt-get install haproxy ssl-cert && \ 8 | apt-get clean && \ 9 | rm -rf /var/lib/apt/lists/* 10 | 11 | WORKDIR /opt/haproxy 12 | 13 | COPY haproxy.cfg run.sh ./ 14 | 15 | VOLUME /opt/haproxy/data 16 | 17 | EXPOSE 80 443 18 | 19 | CMD ["/opt/haproxy/run.sh"] 20 | -------------------------------------------------------------------------------- /docker-compose-multi.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Wrapper to docker-compose for multiple environemnts 3 | 4 | if [[ $# -lt 1 ]]; then 5 | echo "Usage: $0 " >&2 6 | exit 1 7 | fi 8 | 9 | environ="$1" 10 | shift 11 | env_file=".env-${environ}" 12 | 13 | if [[ ! -f "${env_file}" ]]; then 14 | echo "$0: environment file ${env_file} does not exist" >&2 15 | exit 1 16 | fi 17 | 18 | docker-compose --project-name "${environ}" --env-file "${env_file}" "$@" 19 | -------------------------------------------------------------------------------- /haproxy/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Check for certificates and start haproxy 3 | 4 | KeyFile=/etc/ssl/private/ssl-cert-snakeoil.key 5 | PemFile=/etc/ssl/certs/ssl-cert-snakeoil.pem 6 | CertFile=/opt/haproxy/data/ssl/snakeoil.pem 7 | 8 | if [ ! -f ${CertFile} ] 9 | then 10 | echo "*** Generating SSL certificate" 11 | mkdir -p $(dirname ${CertFile}) 12 | make-ssl-cert generate-default-snakeoil --force-overwrite 13 | cat ${KeyFile} ${PemFile} >${CertFile} 14 | fi 15 | 16 | echo "*** Starting haproxy" 17 | exec /usr/sbin/haproxy -f haproxy.cfg 18 | -------------------------------------------------------------------------------- /octoprint/etc/config.yaml: -------------------------------------------------------------------------------- 1 | plugins: 2 | _disabled: 3 | - cura 4 | server: 5 | commands: 6 | serverRestartCommand: /opt/octoprint/bin/restart.sh 7 | systemRestartCommand: /opt/octoprint/bin/reboot.sh 8 | systemShutdownCommand: /opt/octoprint/bin/poweroff.sh 9 | system: 10 | actions: 11 | - action: start-stream 12 | command: /opt/octoprint/bin/streaming.sh start 13 | name: Start video stream 14 | - action: stop-stream 15 | command: /opt/octoprint/bin/streaming.sh stop 16 | name: Stop video stream 17 | webcam: 18 | ffmpeg: /usr/bin/ffmpeg 19 | snapshot: http://webcam:8080/?action=snapshot 20 | stream: /webcam/?action=stream 21 | -------------------------------------------------------------------------------- /webcam/Dockerfile.template: -------------------------------------------------------------------------------- 1 | # Webcam streamer 2 | ARG OP_MACHINE_NAME 3 | 4 | FROM balenalib/${OP_MACHINE_NAME:-%%BALENA_MACHINE_NAME%%}-debian:buster 5 | 6 | RUN apt-get update && \ 7 | apt-get install python3 python3-flask git build-essential subversion \ 8 | libjpeg62-turbo-dev imagemagick ffmpeg libv4l-dev cmake \ 9 | libraspberrypi-dev && \ 10 | apt-get clean && \ 11 | rm -rf /var/lib/apt/lists/* 12 | 13 | WORKDIR /opt/webcam 14 | 15 | RUN git clone https://github.com/jacksonliam/mjpg-streamer.git /opt/webcam/mjpg-streamer && \ 16 | cd mjpg-streamer/mjpg-streamer-experimental && \ 17 | LD_LIBRARY_PATH=. make 18 | 19 | COPY run.py run.py 20 | 21 | EXPOSE 5200 8080 22 | 23 | CMD ["/opt/webcam/run.py"] 24 | -------------------------------------------------------------------------------- /.env-distr: -------------------------------------------------------------------------------- 1 | # Template for the .env configuration file 2 | 3 | # Architecture -- Uncomment one of these to match your hardware 4 | # raspberry-pi is for the Raspberry Pi Zero 5 | # OP_MACHINE_NAME=raspberry-pi 6 | # OP_MACHINE_NAME=raspberry-pi2 7 | OP_MACHINE_NAME=raspberrypi3 8 | 9 | # Webcam configuration 10 | 11 | # Start streaming when the service starts 12 | WEBCAM_START=true 13 | 14 | # mjpg-streamer input plugin 15 | # Default: Raspberry Pi Camera 16 | WEBCAM_INPUT=input_raspicam.so -fps 5 17 | 18 | # USB Webcam example 19 | # WEBCAM_INPUT=input_uvc.so -d /dev/video0 -r 640x480 -fps 5 20 | 21 | # USB Webcam in 16:9 example 22 | # WEBCAM_INPUT=input_uvc.so -d /dev/video0 -r 640x360 -fps 5 23 | 24 | # Listen to the following ports: 25 | # HTTP_PORT=80 26 | # HTTPS_PORT=443 27 | -------------------------------------------------------------------------------- /.env-extra-distr: -------------------------------------------------------------------------------- 1 | # Sample file for driving an extra printer 2 | # 3 | # Copy this file to .env- -- e.g. .env.extra 4 | # To start an additional instance run: 5 | # docker-compose --project-name extra --env-file .env-extra up -d 6 | # or use the convenience script: 7 | # docker-compose-multi.sh extra up -d 8 | 9 | # Architecture -- same as in your main .env file 10 | OP_MACHINE_NAME=raspberrypi3 11 | 12 | # Webcam configuration 13 | # Ensure you use only one Raspberry Pi Camera for all instances. 14 | # Supported configurations: 15 | # - One Raspberry Pi Camera, One USB Camera 16 | # - Two USB cameras 17 | # If you do not have a camera, set "WEBCAM_START=false" 18 | WEBCAM_START=false 19 | WEBCAM_INPUT=input_uvc.so -d /dev/video0 -r 640x480 -fps 5 20 | 21 | # Listen to the following ports -- Must be different for every instances! 22 | HTTP_PORT=8080 23 | HTTPS_PORT=8443 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | services: 4 | - docker 5 | 6 | env: 7 | global: 8 | - DOCKER_COMPOSE_VERSION=1.21.2 9 | matrix: 10 | - OP_MACHINE_NAME=raspberry-pi 11 | - OP_MACHINE_NAME=raspberrypi3 12 | 13 | language: python 14 | 15 | before_install: 16 | - sudo rm /usr/local/bin/docker-compose 17 | - sudo curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose 18 | - sudo chmod +x /usr/local/bin/docker-compose 19 | 20 | install: 21 | - pip install yq 22 | # Ensure we can build ARM images 23 | - docker run --rm --privileged multiarch/qemu-user-static:register --reset 24 | 25 | script: 26 | # Build images 27 | - docker-compose build 28 | # Does it run? 29 | - | 30 | IMAGE=$(docker-compose config | yq -r .services.octoprint.image) 31 | echo "OctoPrint image is: ${IMAGE}" 32 | docker run --rm "${IMAGE}" /opt/octoprint/OctoPrint/venv/bin/octoprint --version 33 | 34 | deploy: 35 | skip_cleanup: true 36 | provider: script 37 | script: ./scripts/deploy.sh 38 | on: 39 | all_branches: true 40 | -------------------------------------------------------------------------------- /haproxy/haproxy.cfg: -------------------------------------------------------------------------------- 1 | global 2 | maxconn 4096 3 | user haproxy 4 | group haproxy 5 | log 127.0.0.1 local1 debug 6 | tune.ssl.default-dh-param 2048 7 | 8 | defaults 9 | log global 10 | mode http 11 | option httplog 12 | option dontlognull 13 | retries 3 14 | option redispatch 15 | option http-server-close 16 | option forwardfor 17 | maxconn 2000 18 | timeout connect 5s 19 | timeout client 15min 20 | timeout server 15min 21 | 22 | frontend public 23 | bind :::80 v4v6 24 | bind :::443 v4v6 ssl crt /opt/haproxy/data/ssl/snakeoil.pem 25 | option forwardfor except 127.0.0.1 26 | use_backend webcam if { path_beg /webcam/ } 27 | default_backend octoprint 28 | 29 | backend octoprint 30 | acl needs_scheme req.hdr_cnt(X-Scheme) eq 0 31 | reqrep ^([^\ :]*)\ /(.*) \1\ /\2 32 | reqadd X-Scheme:\ https if needs_scheme { ssl_fc } 33 | reqadd X-Scheme:\ http if needs_scheme !{ ssl_fc } 34 | option forwardfor 35 | server octoprint1 octoprint:5000 36 | 37 | backend webcam 38 | reqrep ^([^\ :]*)\ /webcam/(.*) \1\ /\2 39 | server webcam1 webcam:8080 40 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Docker-compose file for Octoprint Containers 2 | 3 | # Version 2.1 for balena compatibility 4 | version: '2.1' 5 | 6 | volumes: 7 | octoprint_vol: 8 | 9 | services: 10 | # Octoprint itself 11 | octoprint: 12 | build: 13 | context: octoprint 14 | args: 15 | OP_MACHINE_NAME: 16 | release: latest 17 | image: amedee/octoprint-octoprint 18 | restart: always 19 | privileged: true 20 | volumes: 21 | - octoprint_vol:/opt/octoprint/data 22 | # Uncomment next line for "Plain Docker" setup 23 | # - /run/dbus:/host/run/dbus 24 | labels: 25 | io.balena.features.dbus: '1' 26 | # Webcam stream 27 | webcam: 28 | build: 29 | context: webcam 30 | args: 31 | OP_MACHINE_NAME: 32 | image: amedee/octoprint-webcam 33 | restart: always 34 | privileged: true 35 | environment: 36 | WEBCAM_INPUT: "${WEBCAM_INPUT:-input_raspicam.so -fps 5}" 37 | WEBCAM_START: "${WEBCAM_START:-true}" 38 | # http/https proxy 39 | haproxy: 40 | build: 41 | context: haproxy 42 | args: 43 | OP_MACHINE_NAME: 44 | image: amedee/octoprint-haproxy 45 | restart: always 46 | depends_on: 47 | - octoprint 48 | - webcam 49 | volumes: 50 | - octoprint_vol:/opt/haproxy/data 51 | ports: 52 | - "${HTTP_PORT:-80}:80" 53 | - "${HTTPS_PORT:-443}:443" 54 | -------------------------------------------------------------------------------- /octoprint/Dockerfile.template: -------------------------------------------------------------------------------- 1 | # Octoprint itself 2 | ARG OP_MACHINE_NAME 3 | 4 | FROM balenalib/${OP_MACHINE_NAME:-%%BALENA_MACHINE_NAME%%}-debian:buster 5 | 6 | SHELL ["/bin/bash", "-c"] 7 | 8 | # Balena /bin/sh stub is supposed to cleanup itself, but loops when initally called by apt... 9 | # https://github.com/balena-io-library/base-images/issues/637 10 | RUN [[ -f /bin/sh.real ]] && rm /bin/sh && mv /bin/sh.real /bin/sh 11 | 12 | RUN apt-get update && \ 13 | apt-get install python3 python3-pip python3-dev python3-setuptools \ 14 | python3-virtualenv git libyaml-dev build-essential ffmpeg dbus \ 15 | jq zlib1g-dev libjpeg62-turbo-dev && \ 16 | apt-get clean && \ 17 | rm -rf /var/lib/apt/lists/* 18 | 19 | WORKDIR /opt/octoprint/OctoPrint 20 | 21 | ARG release 22 | 23 | # Travis often fails in getting latest octoprint release, this is why we 24 | # have this while/re-try loop... 25 | RUN if [ "${release}" = "latest" ]; then \ 26 | i=0; \ 27 | while true; do \ 28 | i=$(expr $i + 1); \ 29 | release=$(curl --silent --location https://api.github.com/repos/foosel/OctoPrint/releases/latest | jq -r .tag_name); \ 30 | [ "${release}" = "null" -a $i -lt 10 ] || break;\ 31 | echo "Cannot get release info -- retrying"; \ 32 | sleep 10; \ 33 | done; \ 34 | fi; \ 35 | echo "Building Octoprint ${release}"; \ 36 | git clone --branch "${release}" https://github.com/foosel/OctoPrint.git /opt/octoprint/OctoPrint && \ 37 | python3 -m virtualenv --python=python3 venv && \ 38 | ./venv/bin/pip3 install MarkupSafe && \ 39 | ./venv/bin/python3 setup.py install 40 | 41 | WORKDIR /opt/octoprint 42 | 43 | COPY bin bin/ 44 | COPY etc etc/ 45 | 46 | VOLUME /opt/octoprint/data 47 | 48 | EXPOSE 5000 49 | 50 | CMD ["/opt/octoprint/bin/run.sh"] 51 | -------------------------------------------------------------------------------- /scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Push images to the docker hub 3 | 4 | # Exit on error 5 | set -ev 6 | 7 | if [ "${TRAVIS_PULL_REQUEST}" == "false" ] 8 | then 9 | echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin 10 | # Branch check 11 | if [ "${TRAVIS_BRANCH}" == "master" ] 12 | then 13 | # For the master branch we tag with the octoprint version 14 | IMAGE=$(docker-compose config | yq -r .services.octoprint.image) 15 | TAG=$(docker run --rm "${IMAGE}" /opt/octoprint/OctoPrint/venv/bin/octoprint --version | awk '/version/ && NF==3 {print $NF}') 16 | else 17 | # Otherwhise we just take the branch name 18 | TAG="${TRAVIS_BRANCH}" 19 | fi 20 | 21 | # Architecture 22 | if [ "${OP_MACHINE_NAME}" = "raspberry-pi" ] 23 | then 24 | ARCH=arm32v6 25 | else 26 | ARCH=arm32v7 27 | fi 28 | 29 | # Process all images 30 | IMAGES=$(docker-compose config | yq -r '.services | .[].image') 31 | for IMAGE in ${IMAGES} 32 | do 33 | # Note that we can't publish a manifest dur to https://github.com/moby/moby/issues/34875 34 | # as arm32v6 would always be selected 35 | # for now we promote arm32v7; arm32v6 need to be explicitely selected 36 | echo "Pushing ${IMAGE} to the docker hub. Arch=${ARCH} Tag=${TAG}" 37 | docker tag "${IMAGE}" "${IMAGE}:${ARCH}-${TAG}" 38 | docker push "${IMAGE}:${ARCH}-${TAG}" 39 | if [ "${TRAVIS_BRANCH}" == "master" ] 40 | then 41 | # For the master branch, mark the image as latest 42 | echo "Pushing ${IMAGE} to the docker hub. Arch=${ARCH} Tag=latest" 43 | docker tag "${IMAGE}" "${IMAGE}:${ARCH}-latest" 44 | docker push "${IMAGE}:${ARCH}-latest" 45 | if [ "${ARCH}" = "arm32v7" ] 46 | then 47 | # Promote arm32v7 as default 48 | echo "Promoting arm32v7 ${IMAGE}" 49 | docker push "${IMAGE}" 50 | fi 51 | fi 52 | done 53 | fi 54 | -------------------------------------------------------------------------------- /webcam/run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Simple REST wrapper to start / stop streaming 3 | 4 | import os 5 | from subprocess import Popen 6 | import sys 7 | 8 | from flask import Flask 9 | 10 | base_dir = "/opt/webcam/mjpg-streamer/mjpg-streamer-experimental" 11 | mjpg_input = "input_raspicam.so -fps 5" 12 | mjpg_output = "output_http.so -w " + base_dir + "/www" 13 | app = Flask(__name__) 14 | stream = None 15 | 16 | 17 | def stream_start(): 18 | global stream 19 | if stream: 20 | return "Streaming already running" 21 | else: 22 | stream = Popen([ 23 | os.path.join(base_dir, 'mjpg_streamer'), 24 | '-i', mjpg_input, 25 | '-o', mjpg_output 26 | ], stdout=sys.stdout, stderr=sys.stderr) 27 | return "Streaming started" 28 | 29 | 30 | def stream_stop(): 31 | global stream 32 | if stream: 33 | stream.terminate() 34 | stream.wait() 35 | stream = None 36 | return "Streaming stopped" 37 | else: 38 | return "Streaming not running" 39 | 40 | 41 | @app.route("/on") 42 | def stream_on(): 43 | return stream_start() 44 | 45 | 46 | @app.route("/off") 47 | def stream_off(): 48 | return stream_stop() 49 | 50 | 51 | def initialize(): 52 | global mjpg_input 53 | print("*** Starting WebcamStream") 54 | sys.stdout.flush() 55 | if os.getenv('BALENA'): 56 | # Expunge unexpanded variables from docker-compose 57 | for key, val in os.environ.items(): 58 | if key.startswith('WEBCAM_') and val.startswith('${'): 59 | os.environ.pop(key) 60 | 61 | # Set Library path for mjpg_streamer 62 | if os.getenv('LD_LIBRARY_PATH'): 63 | os.environ['LD_LIBRARY_PATH'] += ':' + base_dir 64 | else: 65 | os.environ['LD_LIBRARY_PATH'] = base_dir 66 | 67 | if os.getenv('WEBCAM_INPUT'): 68 | mjpg_input = os.getenv('WEBCAM_INPUT') 69 | 70 | if os.getenv('WEBCAM_START', 'true') == 'true': 71 | stream_start() 72 | 73 | 74 | if __name__ == '__main__': 75 | initialize() 76 | app.run(debug=False, host="0.0.0.0", port=5200) 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OctoPrint containers [![Build Status](https://travis-ci.com/AmedeeBulle/octoprint-containers.svg?branch=master)](https://travis-ci.com/AmedeeBulle/octoprint-containers) 2 | 3 | # Contents 4 | 5 | 6 | - [Introduction](#introduction) 7 | - [Balena.io setup](#balenaio-setup) 8 | - [Install BalenaOS on your Pi](#install-balenaos-on-your-pi) 9 | - [Configure your OctoPrint device](#configure-your-octoprint-device) 10 | - [Install the software on the Device](#install-the-software-on-the-device) 11 | - [Docker setup](#docker-setup) 12 | - [Prepare the Raspberry Pi](#prepare-the-raspberry-pi) 13 | - [Get the containers](#get-the-containers) 14 | - [Option 1: Download the containers](#option-1-download-the-containers) 15 | - [Option 2: Re-Build the containers](#option-2-re-build-the-containers) 16 | - [Configure and run the OctoPrint server](#configure-and-run-the-octoprint-server) 17 | - [Updates](#updates) 18 | - [First run](#first-run) 19 | - [Note about persistence](#note-about-persistence) 20 | - [Multiple printers](#multiple-printers) 21 | 22 | 23 | # Introduction 24 | 25 | This is a Docker setup for [OctoPrint](https://octoprint.org/) on Raspberry Pi. 26 | It can be run with [balena.io](https://balena.io/) or as _Plain Docker_ on Raspbian. 27 | 28 | The setup is made of 3 containers: 29 | 30 | - `octoprint`: runs the main [OctoPrint](https://octoprint.org/) application 31 | - `webcam`: runs the webcam streaming service (`mjpg-streamer`) 32 | - `haproxy`: exposes the above containers on http and https ports 33 | 34 | The build will use by default the latest [OctoPrint](https://octoprint.org/) release, this can be overridden by changing the `release` argument in the `docker-compose.yml` file. 35 | 36 | This setup will run on any Raspberry Pi, however [OctoPrint](https://octoprint.org/) recommends a Raspberry Pi 3 or 3+. 37 | 38 | # Balena.io setup 39 | 40 | Although it may seem complex at first, [balena.io](https://balena.io/) allows you to install and configure [OctoPrint](https://octoprint.org/) on a Pi in a few clicks. 41 | Also if you have multiple [OctoPrint](https://octoprint.org/) servers, they will be managed from a central place. 42 | 43 | For additional help and nice screenshots of the [balena.io](https://balena.io/) interface look at [Get started with Raspberry Pi 3 and Python](https://docs.balena.io/learn/getting-started/raspberrypi3/python/) on the [balena.io](https://balena.io/) site. 44 | 45 | ## Install BalenaOS on your Pi 46 | 47 | 1. Create an account at [balena.io](https://balena.io/) and sign in 48 | 1. Add your public SSH key to your [balena.io](https://balena.io/) profile 49 | 1. On [balena.io](https://balena.io/), create an "Application" for managing your Pi. 50 | Choose "Raspberry Pi 3" as Device Type. 51 | 1. Add a Device to your Application. 52 | - Configure WiFi here if your Pi is wireless. 53 | - Download the BalenaOS image for your Pi. 54 | 1. Follow the instructions to write the OS on your SD-Card and boot your Pi. 55 | After a while your Pi will appear in your Application Dashboard. 56 | 1. If you like you can change the name of your device. 57 | 58 | ## Configure your OctoPrint device 59 | 60 | The Environment Variables menu "E(x)" allows you to add variables to configure the device for your usage. 61 | 62 | You can add the following variables: 63 | 64 | Name | Default | Description 65 | -------------|----------------------------|------------ 66 | WEBCAM_START | `true` | Start the webcam streaming at boot time.
Use false if you have no webcam or want to start it from the [OctoPrint](https://octoprint.org/) menu 67 | WEBCAM_INPUT | `input_raspicam.so -fps 5` | The input plugin for [`mjpg-streamer`](https://github.com/jacksonliam/mjpg-streamer).
Default is for the Raspberry Pi camera, see the documentation for others.
Example for an USB webcam: `input_uvc.so -d /dev/video0 -r 640x480 -fps 5`. 68 | 69 | ## Install the software on the Device 70 | 71 | The device is now ready, we need to push the containers through [balena.io](https://balena.io/). 72 | The following commands need to be executed from the terminal on your local machine -- __not__ on the Raspberry Pi! 73 | (On Windows, use [Git BASH](https://gitforwindows.org/) or something similar). 74 | 75 | Clone this repository: 76 | 77 | ```shell 78 | git clone https://github.com/AmedeeBulle/octoprint-containers.git 79 | cd octoprint-containers/ 80 | ``` 81 | 82 | Add the address of your [balena.io](https://balena.io/) repository. This command is displayed in the top-left corner of your application dashboard on the web site and looks like: 83 | 84 | ```shell 85 | git remote add balena @git.balena.io:/.git 86 | ``` 87 | 88 | Push the code to [balena.io](https://balena.io/): 89 | 90 | ```shell 91 | git push balena master 92 | ``` 93 | 94 | This will trigger a build on the [balena.io](https://balena.io/) servers. If all goes well it will finish with a nice unicorn 🦄 ASCII art. 95 | Your Raspberry Pi will download and run the containers automatically; after that your [OctoPrint](https://octoprint.org/) server will be ready to go! 96 | 97 | For future updates, you simply need to pull the new code and push it back to [balena.io](https://balena.io/) and your device will be updated! 98 | 99 | ```shell 100 | git pull origin master 101 | git push balena master 102 | ``` 103 | 104 | # Docker setup 105 | 106 | If you do not want to use the [balena.io](https://balena.io/) services, you can run the exact same configuration directly on your Raspberry Pi. 107 | 108 | ## Prepare the Raspberry Pi 109 | 110 | Download and install [Raspbian Buster Lite](https://www.raspberrypi.org/downloads/raspbian/) to your Pi (Follow the instructions from the Foundation). 111 | Although it will work with the full _Desktop_ environment, I strongly recommend the _Lite_ version. 112 | 113 | As root, install `git`, `docker` and `docker-compose`: 114 | 115 | ```shell 116 | apt-get update 117 | apt-get install git curl python-pip 118 | curl -sSL https://get.docker.com | sh 119 | pip install docker-compose 120 | ``` 121 | 122 | Ensure your linux user (`pi` or whatever you choose) is in the `docker` group: 123 | 124 | ```shell 125 | usermod -a -G docker 126 | ``` 127 | 128 | At this point you need to completely logout and re-login to activate the new group. 129 | 130 | From here, __you don't need root access anymore__. 131 | 132 | Clone this repository: 133 | 134 | ```shell 135 | git clone https://github.com/AmedeeBulle/octoprint-containers.git 136 | cd octoprint-containers/ 137 | ``` 138 | 139 | ## Get the containers 140 | 141 | You have 2 options here: download the pre-build containers or re-build them. 142 | 143 | ### Option 1: Download the containers 144 | 145 | This is the easiest and fastest way. The `pull` command will download the containers from the Docker Hub: 146 | 147 | ```shell 148 | docker-compose pull 149 | ``` 150 | 151 | __If you are not using a Raspberry Pi 3__: _multiarch_ build does not work properly on ARM variants (See Moby issue [34875](https://github.com/moby/moby/issues/34875)). 152 | For older Raspberry Pi you need to amend the _docker-compose_ files to pull the correct images: 153 | 154 | ```shell 155 | sed -e 's/\(image:.*\)/\1:arm32v6-latest/' -i.orig docker-compose.yml 156 | ``` 157 | 158 | ### Option 2: Re-Build the containers 159 | 160 | If for whatever reason you want to re-build the containers on your Pi, run: 161 | 162 | ```shell 163 | docker-compose build 164 | ``` 165 | 166 | __If you are not using a Raspberry Pi 3__: copy the `.env-distr` to `.env` and select you Raspberry Pi version. 167 | 168 | ## Configure and run the OctoPrint server 169 | 170 | To customize your setup, create a file named `.env` with the environment variables described in the [balena.io](https://balena.io/) section. You can use the file `.env-distr` as template. 171 | 172 | __Important__: in `docker-compose.yml` uncomment the following line: 173 | 174 | ```yaml 175 | - /run/dbus:/host/run/dbus 176 | ``` 177 | 178 | If you don't do that, you won't be able to restart or shut down you Pi from the [OctoPrint](https://octoprint.org/) user interface. 179 | 180 | Run the [OctoPrint](https://octoprint.org/) server: 181 | 182 | ```shell 183 | docker-compose up 184 | ``` 185 | 186 | This will start the containers and remain attached to your terminal. If everything looks good, you can cancel it and restart the service in detached mode: 187 | 188 | ```shell 189 | docker-compose up -d 190 | ``` 191 | 192 | This will keep he containers running, even after a reboot. 193 | 194 | ## Updates 195 | 196 | To update your setup with a newer version, get the latest code and containers and restart the service: 197 | 198 | ```shell 199 | docker-compose down 200 | git pull origin master 201 | docker-compose pull # or build 202 | docker-compose up -d 203 | ``` 204 | 205 | # First run 206 | 207 | For a _Plain Docker_ setup, you know the IP address of your Pi; if you run [balena.io](https://balena.io/), you will find the address in the application console. 208 | 209 | Point your browser to the IP address of your Raspberry Pi and enjoy [OctoPrint](https://octoprint.org/)! 210 | 211 | At first run, the `haproxy` container will generate a self-signed SSL certificate, so the service will be available on both http and https ports. If you want to share your printer with the world, only expose the https port... 212 | 213 | Enjoy! 214 | 215 | # Note about persistence 216 | 217 | All working files (configuration, G-Code, time-lapses, ...) are stored in the `octoprint_vol` Docker volume, so they won't disappear unless you explicitly destroy the volume. 218 | If you really need/want to destroy the volume and re-start from scratch: 219 | 220 | - [balena.io](https://balena.io/): select 'Purge Data' in the Device Menu 221 | - _Plain Docker_: run 222 | 223 | ```shell 224 | docker-compose down -v 225 | ``` 226 | 227 | The same applies to the containers themselves: they won't be destroyed by default even if you reboot the Pi. To remove existing container and re-create them: 228 | 229 | - [balena.io](https://balena.io/): click on the 'Restart' icon in the Device Dashboard 230 | - _Plain Docker_: run 231 | 232 | ```shell 233 | docker-compose down 234 | docker-compose up -d 235 | ``` 236 | 237 | By doing this, you will loose any change made to the code, in particular if you installed plugins you will have to re-install them (but their configuration will be preserved). 238 | 239 | # Multiple printers 240 | 241 | Although driving multiple printers from the same Raspberry Pi is possible, it might lead to performance issues. 242 | This setup is nevertheless easy to achieve with the _plain Docker_ setup. 243 | It is a good solution if: 244 | 245 | - You have a powerful Raspberry Pi 246 | - You have multiple printers but use only one at a time 247 | 248 | You can run any number of instances of this _container stack_ by using a different _project name_ and a different `.env` file. 249 | The `docker-compose-multi.sh` convenience script is provided to simplify operations. 250 | 251 | Assuming you already have a running instance, to add a new one simply copy the `.env-extra-distr` sample file to `.env-`, 252 | review the configuration parameters and start the instance with `./docker-compose-multi.sh up -d`. 253 | 254 | Main points of attention: 255 | 256 | - Do not configure more than one Raspberry Pi camera! 257 | (There is no limitation on the number of USB cameras) 258 | - Ensure all instances have their own unique HTTP and HTTPS ports. 259 | 260 | Sample session: 261 | 262 | ```shell 263 | # Start the main instance 264 | $ docker-compose up -d 265 | Creating network "octoprint_default" with the default driver 266 | Creating octoprint_octoprint_1 ... done 267 | Creating octoprint_webcam_1 ... done 268 | Creating octoprint_haproxy_1 ... done 269 | $ docker-compose ps 270 | Name Command State Ports 271 | --------------------------------------------------------------------------------------------------------- 272 | octoprint_haproxy_1 /usr/bin/entry.sh /opt/hap ... Up 0.0.0.0:443->443/tcp, 0.0.0.0:80->80/tcp 273 | octoprint_octoprint_1 /usr/bin/entry.sh /opt/oct ... Up 5000/tcp 274 | octoprint_webcam_1 /usr/bin/entry.sh /opt/web ... Up 5200/tcp, 8080/tcp 275 | 276 | # Start an additional instance for the "extra" printer: 277 | $ cp .env-extra-distr .env-extra 278 | $ vi .env-extra 279 | $ ./docker-compose-multi.sh extra up -d 280 | Creating network "extra_default" with the default driver 281 | Creating volume "extra_octoprint_vol" with default driver 282 | Creating extra_webcam_1 ... done 283 | Creating extra_octoprint_1 ... done 284 | Creating extra_haproxy_1 ... done 285 | $ ./docker-compose-multi.sh extra ps 286 | Name Command State Ports 287 | -------------------------------------------------------------------------------------------------------- 288 | extra_haproxy_1 /usr/bin/entry.sh /opt/hap ... Up 0.0.0.0:8443->443/tcp, 0.0.0.0:8080->80/tcp 289 | extra_octoprint_1 /usr/bin/entry.sh /opt/oct ... Up 5000/tcp 290 | extra_webcam_1 /usr/bin/entry.sh /opt/web ... Up 5200/tcp, 8080/tcp 291 | ``` 292 | 293 | Notes: 294 | 295 | - As you can see in the above output, instances use a different network namespace as well as a different volume for storing data, 296 | so they are completely separated and independent. 297 | - Depending on when your printers / cameras are detected they might get different device names on your Raspberry Pi... 298 | Always check that you are driving the right printer! 299 | --------------------------------------------------------------------------------