Table of Contents
- I HA Docker Swarm
-
II Chef’s Favorites (Docker)
- 15 AutoPirate
- 16 SABnzbd
- 17 NZBGet
- 18 RTorrent / ruTorrent
- 19 Sonarr
- 20 Radarr
- 21 Mylar
- 22 LazyLibrarian
- 23 Headphones
- 24 Lidarr
- 25 NZBHydra
- 26 NZBHydra 2
- 27 Ombi
- 28 Jackett
- 29 Heimdall
- 30 Duplicity
- 31 Elkar Backup
- 32 Emby
- 33 Home Assistant
- 34 iBeacons with Home assistant
- 35 Huginn
- 36 Kanboard
- 37 Miniflux
- 38 Munin
- 39 NextCloud
- 40 OwnTracks
- 41 phpIPAM
- 42 Plex
- 43 PrivateBin
- 44 Swarmprom
-
III Recipies (Docker)
- 45 Bitwarden
- 46 BookStack
- 47 Calibre-Web
- 48 Collabora Online
- 49 Ghost
- 50 GitLab
- 51 Gitlab Runner
- 52 Gollum
- 53 InstaPy
- 54 KeyCloak
- 55 Create KeyCloak Users
- 56 Authenticate KeyCloak against OpenLDAP
- 57 Add OIDC Provider to KeyCloak
- 58 OpenLDAP
- 59 Mail Server
- 60 Minio
- 61 Piwik
- 62 Portainer
- 63 Realms
- 64 Tiny Tiny RSS
- 65 Wallabag
- 66 Wekan
- 67 Wetty
- IV Reference
- Notes
1 What is this?
Funky Penguin’s “Geek Cookbook” is a collection of how-to guides for establishing your own container-based self-hosting platform, using either Docker Swarm or Kubernetes.
Running such a platform enables you to run self-hosted tools such as AutoPirate (Radarr, Sonarr, NZBGet and friends), Plex, NextCloud, and includes elements such as:
- Automatic SSL-secured access to all services (with LetsEncrypt)
- SSO / authentication layer to protect unsecured / vulnerable services
- Automated backup of configuration and data
- Monitoring and metrics collection, graphing and alerting
Recent updates and additions are posted on the CHANGELOG, and there’s a friendly community of like-minded geeks in the Discord server.
1.1 Who is this for?
You already have a familiarity with concepts such as virtual machines, Docker containers, LetsEncrypt SSL certificates, databases, and command-line interfaces.
You’ve probably played with self-hosting some mainstream apps yourself, like Plex, NextCloud, Wordpress or Ghost.
1.2 Why should I read this?
So if you’re familiar enough with the concepts above, and you’ve done self-hosting before, why would you read any further?
- You want to upskill. You want to work with container orchestration, Prometheus and Grafana, Kubernetes
- You want to play. You want a safe sandbox to test new tools, keeping the ones you want and tossing the ones you don’t.
- You want reliability. Once you go from playing with a tool to actually using it, you want it to be available when you need it. Having to “quickly ssh into the basement server and restart plex” doesn’t cut it when you finally convince your wife to sit down with you to watch sci-fi.
1.3 What have you done for me lately? (CHANGELOG)
Check out recent change at CHANGELOG
1.4 What do you want from me?
I want your support, either in the financial sense, or as a member of our friendly geek community (or both!)
Get in touch
- Come and say hi to me and the friendly geeks in the Discord chat or the Discourse forums - say hi, ask a question, or suggest a new recipe!
- Tweet me up, I’m @funkypenguin!
- Contact me by a variety of channels
Sponsor / Patronize me
The best way to support this work is to become a GitHub Sponsor / Patreon patron. You get:
- warm fuzzies,
- access to the pre-mix repo,
- an anonymous plug you can pull at any time,
- and a bunch more loot based on tier
.. and I get some pocket money every month to buy wine, cheese, and cryptocurrency!
Impulsively click here (NOW quick do it!) to sponsor me via GitHub, or patronize me via Patreon!
Work with me
Need some Cloud / Microservices / DevOps / Infrastructure design work done? I’m a full-time AWS-certified consultant, this stuff is my bread and butter! :breadfork_and_knife: Get in touch, and let’s talk business!
By the time I had enlisted Funky Penguin’s help, I’d architected myself into a bit of a nightmare with Kubernetes. I knew what I wanted to achieve, but I’d made a mess of it. Funky Penguin (David) was able to jump right in and offer a vital second-think on everything I’d done, pointing out where things could be simplified and streamlined, and better alternatives.He unblocked me on all the technical hurdles to launching my SaaS in GKE!
With him delivering the container/Kubernetes architecture and helm CI/CD workflow, I was freed up to focus on coding and design, which fast-tracked me to launching on time. And now I have a simple deployment process that is easy for me to execute and maintain as a solo founder.
I have no hesitation in recommending him for your project, and I’ll certainly be calling on him again in the future.
– John McDowall, Founder, kiso.io
Buy my book
I’m publishing the Geek Cookbook as a formal eBook (PDF, mobi, epub), on Leanpub (https://leanpub.com/geek-cookbook). Check it out!
2 How to read this book
2.1 Structure
- “Recipes” generally follow on from each other. I.e., if a particular recipe requires a mail server, that mail server would have been described in an earlier recipe.
- Each recipe contains enough detail in a single page to take a project from start to completion.
- When there are optional add-ons/integrations possible to a project (i.e., the addition of “smart LED bulbs” to Home Assistant), this will be reflected either as a brief “Chef’s note” after the recipe, or if they’re substantial enough, as a sub-page of the main project
2.2 Conventions
- When creating swarm networks, we always explicitly set the subnet in the overlay network, to avoid potential conflicts (which docker won’t prevent, but which will generate errors) (https://github.com/moby/moby/issues/26912)
3 CHANGELOG
3.1 Subscribe to updates
- Email : Sign up here (double-opt-in) to receive email updates on new and improve recipes!
- Mastodon: https://mastodon.social/@geekcookbook_changes
- RSS: https://mastodon.social/@geekcookbook_changes.rss
- The #changelog channel in our Discord server
3.2 Recent additions to work-in-progress
- Kubernetes recipes for UniFi controller, Miniflux, Kanboard and PrivateBin coming in March! (19 Mar 2019)
3.3 Recently added recipes
- Overhauled Ceph (Shared Storage) recipe for Ceph Octopus (v15) (25 May 2020)
- Added recipe for making your own DIY Kubernetes Cluster (14 December 2019)
- Added recipe for authenticating Traefik Forward Auth against KeyCloak (16 May 2019)
- Added Bitwarden, an awesome open-source password manager, with great mobile sync support (14 May 2019)
- Added Traefik Forward Auth, replacing function of multiple oauth_proxies with a single, 7MB Go application, which can authenticate against Google, KeyCloak, and other OIDC providers (10 May 2019)
3.4 Recent improvements
- Added recipe for automated snapshots of Kubernetes Persistent Volumes, instructions for using Helm, and recipe for deploying Traefik, which completes the Kubernetes cluster design! (9 Feb 2019)
- Added detailed description (and diagram) of our Kubernetes design, plus a simple load-balancer design to avoid the complexities/costs of permitting ingress access to a cluster (7 Feb 2019)
- Added an introductory/explanatory page, including a children’s story, on Kubernetes (29 Jan 2019)
- NextCloud updated to fix CalDAV/CardDAV service discovery behind Traefik reverse proxy (12 Dec 2018)
4 Welcome to Funky Penguin’s Geek Cookbook
4.1 Hello world,
I’m David.
I’m a contracting IT consultant, with a broad range of experience and skills. I’m an AWS Certified Solution Architect (Professional), a remote worker, I’ve had a book published, and I blog on topics that interest me.
4.2 Why Funky Penguin?
My first “real” job, out of high-school, was working the IT helpdesk in a typical pre-2000 organization in South Africa. I enjoyed experimenting with Linux, and cut my teeth by replacing the organization’s Exchange 5.5 mail platform with a 15-site qmail-ldap cluster, with amavis virus-scanning.
One of our suppliers asked me to quote to do the same for their organization. With nothing to loose, and half-expecting to be turned down, I quoted a generous fee, and chose a cheeky company name. The supplier immediately accepted my quote, and the name (“Funky Penguin”) stuck.
4.3 Technical Documentation
During the same “real” job above, I wanted to deploy jabberd, for internal instant messaging within the organization, and as a means to control the sprawl of ad-hoc instant-messaging among staff, using ICQ, MSN, and Yahoo Messenger.
To get management approval to deploy, I wrote a logger (with web UI) for jabber conversations (Bandersnatch), and a 75-page user manual (in Docbook XML) for a spunky Russian WinXP jabber client, JAJC.
Due to my contributions to phpList, I was approached in 2011 by Packt Publishing, to write a book about using PHPList.
4.4 Work with me
Need some Cloud / Microservices / DevOps / Infrastructure design work done? I’m a full-time [AWS-certified][aws_cert] consultant, this stuff is my bread and butter! :breadfork_and_knife: [Get in touch][contact], and let’s talk business!
[plex]: https://www.plex.tv/
[nextcloud]: https://nextcloud.com/
[wordpress]: https://wordpress.org/
[ghost]: https://ghost.io/
[discord]: http://chat.funkypenguin.co.nz
[patreon]: https://www.patreon.com/bePatron?u=6982506
[github_sponsor]: https://github.com/sponsors/funkypenguin
[github]: https://github.com/sponsors/funkypenguin
[discourse]: https://discourse.geek-kitchen.funkypenguin.co.nz/
[twitter]: https://twitter.com/funkypenguin
[contact]: https://www.funkypenguin.co.nz
[aws_cert]: https://www.certmetrics.com/amazon/public/badge.aspx?i=4&t=c&d=2019-02-22&ci=AWS00794574
He unblocked me on all the technical hurdles to launching my SaaS in GKE!
With him delivering the container/Kubernetes architecture and helm CI/CD workflow, I was freed up to focus on coding and design, which fast-tracked me to launching on time. And now I have a simple deployment process that is easy for me to execute and maintain as a solo founder.
I have no hesitation in recommending him for your project, and I’ll certainly be calling on him again in the future.
- John McDowall, Founder, kiso.io
4.5 Contact Me
Contact me by:
- Jumping into our Discord server
- Email (davidy@funkypenguin.co.nz)
- Twitter (@funkypenguin)
Or by using the form below:
<div class=”panel”>
<iframe width=”100%” height=”400” frameborder=”0” scrolling=”no” src=”https://funkypenguin.wufoo.com/forms/z16038vt0bk5txp/”></iframe>
</div>
I HA Docker Swarm
This section introduces the HA Docker Swarm, which will be the basis for all the recipes discussed.
5 Design
In the design described below, our “private cloud” platform is:
- Highly-available (can tolerate the failure of a single component)
- Scalable (can add resource or capacity as required)
- Portable (run it on your garage server today, run it in AWS tomorrow)
- Secure (access protected with LetsEncrypt certificates and optional OIDC with 2FA)
- Automated (requires minimal care and feeding)
5.1 Design Decisions
Where possible, services will be highly available.
This means that:
- At least 3 docker swarm manager nodes are required, to provide fault-tolerance of a single failure.
- Ceph is employed for share storage, because it too can be made tolerant of a single failure.
Where multiple solutions to a requirement exist, preference will be given to the most portable solution.
This means that:
- Services are defined using docker-compose v3 YAML syntax
- Services are portable, meaning a particular stack could be shut down and moved to a new provider with minimal effort.
5.2 Security
Under this design, the only inbound connections we’re permitting to our docker swarm in a minimal configuration (you may add custom services later, like UniFi Controller) are:
Network Flows
- HTTP (TCP 80) : Redirects to https
- HTTPS (TCP 443) : Serves individual docker containers via SSL-encrypted reverse proxy
Authentication
- Where the hosted application provides a trusted level of authentication (i.e., NextCloud), or where the application requires public exposure (i.e. Privatebin), no additional layer of authentication will be required.
- Where the hosted application provides inadequate (i.e. NZBGet) or no authentication (i.e. Gollum), a further authentication against an OAuth provider will be required.
5.3 High availability
Normal function
Assuming a 3-node configuration, under normal circumstances the following is illustrated:
- All 3 nodes provide shared storage via Ceph, which is provided by a docker container on each node.
- All 3 nodes participate in the Docker Swarm as managers.
- The various containers belonging to the application “stacks” deployed within Docker Swarm are automatically distributed amongst the swarm nodes.
- Persistent storage for the containers is provide via cephfs mount.
- The traefik service (in swarm mode) receives incoming requests (on HTTP and HTTPS), and forwards them to individual containers. Traefik knows the containers names because it’s able to read the docker socket.
- All 3 nodes run keepalived, at varying priorities. Since traefik is running as a swarm service and listening on TCP 80/443, requests made to the keepalived VIP and arriving at any of the swarm nodes will be forwarded to the traefik container (no matter which node it’s on), and then onto the target backend.
Node failure
In the case of a failure (or scheduled maintenance) of one of the nodes, the following is illustrated:
- The failed node no longer participates in Ceph, but the remaining nodes provide enough fault-tolerance for the cluster to operate.
- The remaining two nodes in Docker Swarm achieve a quorum and agree that the failed node is to be removed.
- The (possibly new) leader manager node reschedules the containers known to be running on the failed node, onto other nodes.
- The traefik service is either restarted or unaffected, and as the backend containers stop/start and change IP, traefik is aware and updates accordingly.
- The keepalived VIP continues to function on the remaining nodes, and docker swarm continues to forward any traffic received on TCP 80/443 to the appropriate node.
Node restore
When the failed (or upgraded) host is restored to service, the following is illustrated:
- Ceph regains full redundancy
- Docker Swarm managers become aware of the recovered node, and will use it for scheduling new containers
- Existing containers which were migrated off the node are not migrated backend
- Keepalived VIP regains full redundancy
Total cluster failure
A day after writing this, my environment suffered a fault whereby all 3 VMs were unexpectedly and simultaneously powered off.
Upon restore, docker failed to start on one of the VMs due to local disk space issue1. However, the other two VMs started, established the swarm, mounted their shared storage, and started up all the containers (services) which were managed by the swarm.
In summary, although I suffered an unplanned power outage to all of my infrastructure, followed by a failure of a third of my hosts… ==all my platforms are 100% available with absolutely no manual intervention==.
5.4 Chef’s Notes
6 Nodes
Let’s start building our cluster. You can use either bare-metal machines or virtual machines - the configuration would be the same. To avoid confusion, I’ll be referring to these as “nodes” from now on.
In 2017, I initially chose the “Atomic” CentOS/Fedora image for the swarm hosts, but later found its outdated version of Docker to be problematic with advanced features like GPU transcoding (in Plex), Swarmprom, etc. In the end, I went mainstream and simply preferred a modern Ubuntu installation.6.1 Ingredients
New in this recipe:* [ ] 3 x nodes (bare-metal or VMs), each with:
* A mainstream Linux OS (tested on either CentOS 7+ or Ubuntu 16.04+)
* At least 2GB RAM
* At least 20GB disk space (but it’ll be tight)
* [ ] Connectivity to each other within the same subnet, and on a low-latency link (i.e., no WAN links)
6.2 Preparation
Permit connectivity
Most modern Linux distributions include firewall rules which only only permit minimal required incoming connections (like SSH). We’ll want to allow all traffic between our nodes. The steps to achieve this in CentOS/Ubuntu are a little different…
CentOS
Add something like this to /etc/sysconfig/iptables
:
And restart iptables with systemctl restart iptables
Ubuntu
Install the (non-default) persistent iptables tools, by running apt-get install iptables-persistent
, establishing some default rules (dkpg will prompt you to save current ruleset), and then add something like this to /etc/iptables/rules.v4
:
And refresh your running iptables rules with iptables-restore < /etc/iptables/rules.v4
Enable hostname resolution
Depending on your hosting environment, you may have DNS automatically setup for your VMs. If not, it’s useful to set up static entries in /etc/hosts for the nodes. For example, I setup the following:
Set timezone
Set your local timezone, by running:
6.3 Serving
After completing the above, you should have:
Deployed in this recipe:* [X] 3 x nodes (bare-metal or VMs), each with:
* A mainstream Linux OS (tested on either CentOS 7+ or Ubuntu 16.04+)
* At least 2GB RAM
* At least 20GB disk space (but it’ll be tight)
* [X] Connectivity to each other within the same subnet, and on a low-latency link (i.e., no WAN links)
6.4 Chef’s Notes
7 Shared Storage (Ceph)
While Docker Swarm is great for keeping containers running (and restarting those that fail), it does nothing for persistent storage. This means if you actually want your containers to keep any data persistent across restarts (hint: you do!), you need to provide shared storage to every docker node.
7.1 Ingredients
3 x Virtual Machines (configured earlier), each with:* [X] Support for “modern” versions of Python and LVM
* [X] At least 1GB RAM
* [X] At least 20GB disk space (but it’ll be tight)
* [X] Connectivity to each other within the same subnet, and on a low-latency link (i.e., no WAN links)
* [X] A second disk dedicated to the Ceph OSD
* [X] Each node should have the IP of every other participating node hard-coded in /etc/hosts (including its own IP)
7.2 Preparation
Earlier iterations of this recipe (based on Ceph Jewel) required significant manual effort to install Ceph in a Docker environment. In the 2+ years since Jewel was released, significant improvements have been made to the ceph “deploy-in-docker” process, including the introduction of the cephadm tool. Cephadm is the tool which now does all the heavy lifting, below, for the current version of ceph, codenamed “Octopus”.Pick a master node
One of your nodes will become the cephadm “master” node. Although all nodes will participate in the Ceph cluster, the master node will be the node which we bootstrap ceph on. It’s also the node which will run the Ceph dashboard, and on which future upgrades will be processed. It doesn’t matter which node you pick, and the cluster itself will operate in the event of a loss of the master node (although you won’t see the dashboard)
Install cephadm on master node
Run the following on the ==master== node:
The process takes about 30 seconds, after which, you’ll have a MVC (Minimum Viable Cluster)1, encompassing a single monitor and mgr instance on your chosen node. Here’s the complete output from a fresh install:
??? “Example output from a fresh cephadm bootstrap”
Prepare other nodes
It’s now necessary to tranfer the following files to your ==other== nodes, so that cephadm can add them to your cluster, and so that they’ll be able to mount the cephfs when we’re done:
Path on master | Path on non-master |
---|---|
/etc/ceph/ceph.conf |
/etc/ceph/ceph.conf |
/etc/ceph/ceph.client.admin.keyring |
/etc/ceph/ceph.client.admin.keyring |
/etc/ceph/ceph.pub |
/root/.ssh/authorized_keys (append to anything existing) |
Back on the ==master== node, run ceph orch host add <node-name>
once for each other node you want to join to the cluster. You can validate the results by running ceph orch host ls
/root/.ssh
), so worrying about cephadm seems a little barn-door-after-horses-bolted. If you take host-level security seriously, consider switching to Kubernetes :) Add OSDs
Now the best improvement since the days of ceph-deploy and manual disks.. on the ==master== node, run ceph orch apply osd --all-available-devices
. This will identify any unloved (unpartitioned, unmounted) disks attached to each participating node, and configure these disks as OSDs.
Setup CephFS
On the ==master== node, create a cephfs volume in your cluster, by running ceph fs volume create data
. Ceph will handle the necessary orchestration itself, creating the necessary pool, mds daemon, etc.
You can watch the progress by running ceph fs ls
(to see the fs is configured), and ceph -s
to wait for HEALTH_OK
Mount CephFS volume
On ==every== node, create a mountpoint for the data, by running mkdir /var/data
, add an entry to fstab to ensure the volume is auto-mounted on boot, and ensure the volume is actually mounted if there’s a network / boot delay getting access to the gluster volume:
7.3 Serving
Sprinkle with tools
Although it’s possible to use cephadm shell
to exec into a container with the necessary ceph tools, it’s more convenient to use the native CLI tools. To this end, on each node, run the following, which will install the appropriate apt repository, and install the latest ceph CLI tools:
Drool over dashboard
Ceph now includes a comprehensive dashboard, provided by the mgr daemon. The dashboard will be accessible at https://[IP of your ceph master node]:8443, but you’ll need to run ceph dashboard ac-user-create <username> <password> administrator
first, to create an administrator account:
7.4 Summary
What have we achieved?
Created:* [X] Persistent storage available to every node
* [X] Resiliency in the event of the failure of a single node
* [X] Beautiful dashboard
7.5 The easy, 5-minute install
I share (with [sponsors][github_sponsor] and [patrons][patreon]) a private “premix” GitHub repository, which includes an ansible playbook for deploying the entire Geek’s Cookbook stack, automatically. This means that members can create the entire environment with just a git pull
and an ansible-playbook deploy.yml
Here’s a screencast of the playbook in action. I sped up the boring parts, it actually takes ==5 min== (you can tell by the timestamps on the prompt):
[patreon]: https://www.patreon.com/bePatron?u=6982506
[github_sponsor]: https://github.com/sponsors/funkypenguin
7.6 Chef’s Notes
8 Shared Storage (GlusterFS)
While Docker Swarm is great for keeping containers running (and restarting those that fail), it does nothing for persistent storage. This means if you actually want your containers to keep any data persistent across restarts (hint: you do!), you need to provide shared storage to every docker node.
This recipe is deprecated. It didn’t work well in 2017, and it’s not likely to work any better now. It remains here as a reference. I now recommend the use of Ceph for shared storage instead. - 2019 Chef8.1 Design
Why GlusterFS?
This GlusterFS recipe was my original design for shared storage, but I found it to be flawed, and I replaced it with a design which employs Ceph instead. This recipe is an alternate to the Ceph design, if you happen to prefer GlusterFS.
8.2 Ingredients
3 x Virtual Machines (configured earlier), each with:* [X] CentOS/Fedora Atomic
* [X] At least 1GB RAM
* [X] At least 20GB disk space (but it’ll be tight)
* [X] Connectivity to each other within the same subnet, and on a low-latency link (i.e., no WAN links)
* [ ] A second disk, or adequate space on the primary disk for a dedicated data partition
8.3 Preparation
Create Gluster “bricks”
To build our Gluster volume, we need 2 out of the 3 VMs to provide one “brick”. The bricks will be used to create the replicated volume. Assuming a replica count of 2 (i.e., 2 copies of the data are kept in gluster), our total number of bricks must be divisible by our replica count. (I.e., you can’t have 3 bricks if you want 2 replicas. You can have 4 though - We have to have minimum 3 swarm manager nodes for fault-tolerance, but only 2 of those nodes need to run as gluster servers.)
On each host, run a variation following to create your bricks, adjusted for the path to your disk.
Atomic uses LVM to store docker data, and automatically grows Docker’s volumes as requried. If you commit all your free LVM space to your brick, you’ll quickly find (as I did) that docker will start to fail with error messages about insufficient space. If you’re going to slice off a portion of your LVM space in /dev/atomicos, make sure you leave enough space for Docker storage, where “enough” depends on how much you plan to pull images, make volumes, etc. I ate through 20GB very quickly doing development, so I ended up provisioning 50GB for atomic alone, with a separate volume for the brick.Create glusterfs container
Atomic doesn’t include the Gluster server components. This means we’ll have to run glusterd from within a container, with privileged access to the host. Although convoluted, I’ve come to prefer this design since it once again makes the OS “disposable”, moving all the config into containers and code.
Run the following on each host:
Create trusted pool
On a single node (doesn’t matter which), run docker exec -it glusterfs-server bash
to launch a shell inside the container.
From the node, run gluster peer probe <other host>
.
Example output:
Run gluster peer status
on both nodes to confirm that they’re properly connected to each other:
Example output:
Create gluster volume
Now we create a replicated volume out of our individual “bricks”.
Create the gluster volume by running:
Example output:
Start the volume by running gluster volume start gv0
The volume is only present on the host you’re shelled into though. To add the other hosts to the volume, run gluster peer probe <servername>
. Don’t probe host from itself.
From one other host, run docker exec -it glusterfs-server bash
to shell into the gluster-server container, and run gluster peer probe <original server name>
to update the name of the host which started the volume.
Mount gluster volume
On the host (i.e., outside of the container - type exit
if you’re still shelled in), create a mountpoint for the data, by running mkdir /var/data
, add an entry to fstab to ensure the volume is auto-mounted on boot, and ensure the volume is actually mounted if there’s a network / boot delay getting access to the gluster volume:
For some reason, my nodes won’t auto-mount this volume on boot. I even tried the trickery below, but they stubbornly refuse to automount:
For non-gluster nodes, you’ll need to replace $MYHOST above with the name of one of the gluster hosts (I haven’t worked out how to make this fully HA yet)
8.4 Serving
After completing the above, you should have:
- [X] Persistent storage available to every node
- [X] Resiliency in the event of the failure of a single (gluster) node
8.5 Chef’s Notes
Future enhancements to this recipe include:
- Migration of shared storage from GlusterFS to Ceph ()#2)
- Correct the fact that volumes don’t automount on boot (#3)
9 Keepalived
While having a self-healing, scalable docker swarm is great for availability and scalability, none of that is any good if nobody can connect to your cluster.
In order to provide seamless external access to clustered resources, regardless of which node they’re on and tolerant of node failure, you need to present a single IP to the world for external access.
Normally this is done using a HA loadbalancer, but since Docker Swarm aready provides the load-balancing capabilities (routing mesh), all we need for seamless HA is a virtual IP which will be provided by more than one docker node.
This is accomplished with the use of keepalived on at least two nodes.
9.1 Ingredients
Already deployed:* [X] At least 2 x swarm nodes
* [X] low-latency link (i.e., no WAN links)
New:
* [ ] At least 3 x IPv4 addresses (one for each node and one for the virtual IP)
9.2 Preparation
Enable IPVS module
On all nodes which will participate in keepalived, we need the “ip_vs” kernel module, in order to permit serivces to bind to non-local interface addresses.
Set this up once for both the primary and secondary nodes, by running:
Setup nodes
Assuming your IPs are as follows:
- 192.168.4.1 : Primary
- 192.168.4.2 : Secondary
- 192.168.4.3 : Virtual
Run the following on the primary
And on the secondary:
9.3 Serving
That’s it. Each node will talk to the other via unicast (no need to un-firewall multicast addresses), and the node with the highest priority gets to be the master. When ingress traffic arrives on the master node via the VIP, docker’s routing mesh will deliver it to the appropriate docker node.
9.4 Chef’s notes
- Some hosting platforms (OpenStack, for one) won’t allow you to simply “claim” a virtual IP. Each node is only able to receive traffic targetted to its unique IP, unless certain security controls are disabled by the cloud administrator. In this case, keepalived is not the right solution, and a platform-specific load-balancing solution should be used. In OpenStack, this is Neutron’s “Load Balancer As A Service” (LBAAS) component. AWS, GCP and Azure would likely include similar protections.
- More than 2 nodes can participate in keepalived. Simply ensure that each node has the appropriate priority set, and the node with the highest priority will become the master.
10 Docker Swarm Mode
For truly highly-available services with Docker containers, we need an orchestration system. Docker Swarm (as defined at 1.13) is the simplest way to achieve redundancy, such that a single docker host could be turned off, and none of our services will be interrupted.
10.1 Ingredients
Existing* [X] 3 x nodes (bare-metal or VMs), each with:
* A mainstream Linux OS (tested on either CentOS 7+ or Ubuntu 16.04+)
* At least 2GB RAM
* At least 20GB disk space (but it’ll be tight)
* [X] Connectivity to each other within the same subnet, and on a low-latency link (i.e., no WAN links)
10.2 Preparation
Bash auto-completion
Add some handy bash auto-completion for docker. Without this, you’ll get annoyed that you can’t autocomplete docker stack deploy <blah> -c <blah.yml>
commands.
Install some useful bash aliases on each host
10.3 Serving
Release the swarm!
Now, to launch a swarm. Pick a target node, and run docker swarm init
Yeah, that was it. Seriously. Now we have a 1-node swarm.
Run docker node ls
to confirm that you have a 1-node swarm:
Note that when you run docker swarm init
above, the CLI output gives youe a command to run to join further nodes to my swarm. This command would join the nodes as workers (as opposed to managers). Workers can easily be promoted to managers (and demoted again), but since we know that we want our other two nodes to be managers too, it’s simpler just to add them to the swarm as managers immediately.
On the first swarm node, generate the necessary token to join another manager by running docker swarm join-token manager
:
Run the command provided on your other nodes to join them to the swarm as managers. After addition of a node, the output of docker node ls
(on either host) should reflect all the nodes:
Setup automated cleanup
Docker swarm doesn’t do any cleanup of old images, so as you experiment with various stacks, and as updated containers are released upstream, you’ll soon find yourself loosing gigabytes of disk space to old, unused images.
To address this, we’ll run the “meltwater/docker-cleanup” container on all of our nodes. The container will clean up unused images after 30 minutes.
First, create docker-cleanup.env (mine is under /var/data/config/docker-cleanup), and exclude container images we know we want to keep:
Then create a docker-compose.yml as follows:
Setup unique static subnets for every stack you deploy. This avoids IP/gateway conflicts which can otherwise occur when you’re creating/removing stacks a lot. See my list here.Launch the cleanup stack by running docker stack deploy docker-cleanup -c <path-to-docker-compose.yml>
Setup automatic updates
If your swarm runs for a long time, you might find yourself running older container images, after newer versions have been released. If you’re the sort of geek who wants to live on the edge, configure shepherd to auto-update your container images regularly.
Create /var/data/config/shepherd/shepherd.env as follows:
Then create /var/data/config/shepherd/shepherd.yml as follows:
Launch shepherd by running docker stack deploy shepherd -c /var/data/config/shepherd/shepherd.yml
, and then just forget about it, comfortable in the knowledge that every day, Shepherd will check that your images are the latest available, and if not, will destroy and recreate the container on the latest available image.
Summary
After completing the above, you should have:
10.4 Chef’s Notes
11 Traefik
The platforms we plan to run on our cloud are generally web-based, and each listening on their own unique TCP port. When a container in a swarm exposes a port, then connecting to any swarm member on that port will result in your request being forwarded to the appropriate host running the container. (Docker calls this the swarm “routing mesh“)
So we get a rudimentary load balancer built into swarm. We could stop there, just exposing a series of ports on our hosts, and making them HA using keepalived.
There are some gaps to this approach though:
- No consideration is given to HTTPS. Implementation would have to be done manually, per-container.
- No mechanism is provided for authentication outside of that which the container providers. We may not want to expose every interface on every container to the world, especially if we are playing with tools or containers whose quality and origin are unknown.
To deal with these gaps, we need a front-end load-balancer, and in this design, that role is provided by Traefik.
11.1 Ingredients
Existing* [X] Docker swarm cluster with persistent shared storage
New
* [ ] Access to update your DNS records for manual/automated LetsEncrypt DNS-01 validation, or ingress HTTP/HTTPS for HTTP-01 validation
11.2 Preparation
Prepare the host
The traefik container is aware of the other docker containers in the swarm, because it has access to the docker socket at /var/run/docker.sock. This allows traefik to dynamically configure itself based on the labels found on containers in the swarm, which is hugely useful. To make this functionality work on a SELinux-enabled CentOS7 host, we need to add custom SELinux policy.
The following is only necessary if you’re using SELinux!Run the following to build and activate policy to permit containers to access docker.sock:
Prepare traefik.toml
While it’s possible to configure traefik via docker command arguments, I prefer to create a config file (traefik.toml
). This allows me to change traefik’s behaviour by simply changing the file, and keeps my docker config simple.
Create /var/data/traefik/traefik.toml
as follows:
Prepare the docker service config
“We’ll want an overlay network, independent of our traefik stack, so that we can attach/detach all our other stacks (including traefik) to the overlay network. This way, we can undeploy/redepoly the traefik stack without having to bring every other stack first!” - voice of experienceCreate /var/data/config/traefik/traefik.yml
as follows:
git pull
and a docker stack deploy
Create /var/data/config/traefik/traefik-app.yml
as follows:
Docker won’t start a service with a bind-mount to a non-existent file, so prepare an empty acme.json (with the appropriate permissions) by running:
Pay attention above. You must setacme.json
’s permissions to owner-readable-only, else the container will fail to start with an ID-10T error!Traefik will populate acme.json itself when it runs, but it needs to exist before the container will start (Chicken, meet egg.)
11.3 Serving
Launch
First, launch the traefik stack, which will do nothing other than create an overlay network by running docker stack deploy traefik -c /var/data/traefik/traefik.yml
Now deploy the traefik appliation itself (which will attach to the overlay network) by running docker stack deploy traefik-app -c /var/data/traefik/traefik-app.yml
Confirm traefik is running with docker stack ps traefik-app
:
Check Traefik Dashboard
You should now be able to access your traefik instance on http://<node IP>:8080 - It’ll look a little lonely currently (below), but we’ll populate it as we add recipes :)
Summary
We’ve achieved:* [X] An overlay network to permit traefik to access all future stacks we deploy
* [X] Frontend proxy which will dynamically configure itself for new backend containers
* [X] Automatic SSL support for all proxied resources
11.4 Chef’s Notes
- Did you notice how no authentication was required to view the Traefik dashboard? Eek! We’ll tackle that in the next section, regarding Traefik Forward Authentication!
12 Traefik Forward Auth
Now that we have Traefik deployed, automatically exposing SSL access to our Docker Swarm services using LetsEncrypt wildcard certificates, let’s pause to consider that we may not want some services exposed directly to the internet…
..Wait, why not? Well, Traefik doesn’t provide any form of authentication, it simply secures the transmission of the service between Docker Swarm and the end user. If you were to deploy a service with no native security (Radarr or Sonarr come to mind), then anybody would be able to use it! Even services which may have a layer of authentication might not be safe to expose publically - often open source projects may be maintained by enthusiasts who happily add extra features, but just pay lip service to security, on the basis that “it’s the user’s problem to secure it in their own network”.
To give us confidence that we can access our services, but BadGuys(tm) cannot, we’ll deploy a layer of authentication in front of Traefik, using Forward Authentication. You can use your own KeyCloak instance for authentication, but to lower the barrier to entry, this recipe will assume you’re authenticating against your own Google account.
12.1 Ingredients
Existing:* [X] Docker swarm cluster with persistent shared storage
* [X] Traefik configured per design
New:
* [ ] Client ID and secret from an OpenID-Connect provider (Google, KeyCloak, Microsoft, etc..)
12.2 Preparation
Obtain OAuth credentials
This recipe will demonstrate using Google OAuth for traefik forward authentication, but it’s also possible to use a self-hosted KeyCloak instance - see the KeyCloak OIDC Provider recipe for more details!Log into https://console.developers.google.com/, create a new project then search for and select “Credentials” in the search bar.
Fill out the “OAuth Consent Screen” tab, and then click, “Create Credentials” > “OAuth client ID”. Select “Web Application”, fill in the name of your app, skip “Authorized JavaScript origins” and fill “Authorized redirect URIs” with either all the domains you will allow authentication from, appended with the url-path (e.g. https://radarr.example.com/_oauth, https://radarr.example.com/_oauth, etc), or if you don’t like frustration, use a “auth host” URL instead, like “https://auth.example.com/_oauth” (see below for details)
Store your client ID and secret safely - you’ll need them for the next step.
Prepare environment
Create /var/data/config/traefik/traefik-forward-auth.env
as follows:
Prepare the docker service config
This is a small container, you can simply add the following content to the existing traefik-app.yml
deployed in the previous Traefik recipe:
If you’re not confident that forward authentication is working, add a simple “whoami” test container, to help debug traefik forward auth, before attempting to add it to a more complex container.
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
12.3 Serving
Launch
Redeploy traefik with docker stack deploy traefik-app -c /var/data/traefik/traeifk-app.yml
, to launch the traefik-forward-auth container.
Test
Browse to https://whoami.example.com (obviously, customized for your domain and having created a DNS record), and all going according to plan, you should be redirected to a Google login. Once successfully logged in, you’ll be directed to the basic whoami page.
12.4 Summary
What have we achieved? By adding an additional three simple labels to any service, we can secure any service behind our choice of OAuth provider, with minimal processing / handling overhead.
Created:* [X] Traefik-forward-auth configured to authenticate against an OIDC provider
12.5 Chef’s Notes
- Traefik forward auth replaces the use of oauth_proxy containers found in some of the existing recipes
- @thomaseddon’s original version of traefik-forward-auth only works with Google currently, but I’ve created a fork of a fork, which implements generic OIDC providers.
- I reviewed several implementations of forward authenticators for Traefik, but found most to be rather heavy-handed, or specific to a single auth provider. @thomaseddon’s go-based docker image is 7MB in size, and with the generic OIDC patch (above), it can be extended to work with any OIDC provider.
- No, not github natively, but you can ferderate GitHub into KeyCloak, and then use KeyCloak as the OIDC provider.
13 Using Traefik Forward Auth with KeyCloak
While the Traefik Forward Auth recipe demonstrated a quick way to protect a set of explicitly-specified URLs using OIDC credentials from a Google account, this recipe will illustrate how to use your own KeyCloak instance to secure any URLs within your DNS domain.
13.1 Ingredients
Existing:* [X] KeyCloak recipe deployed successfully, with a local user and an OIDC client
New:
* [ ] DNS entry for your auth host (“auth.yourdomain.com” is a good choice), pointed to your keepalived IP
13.2 Preparation
What is AuthHost mode
Under normal OIDC auth, you have to tell your auth provider which URLs it may redirect an authenticated user back to, post-authentication. This is a security feture of the OIDC spec, preventing a malicious landing page from capturing your session and using it to impersonate you. When you’re securing many URLs though, explicitly listing them can be a PITA.
@thomaseddon’s traefik-forward-auth includes an ingenious mechanism to simulate an “auth host” in your OIDC authentication, so that you can protect an unlimited amount of DNS names (with a common domain suffix), without having to manually maintain a list.
How does it work?
Say you’re protecting radarr.example.com. When you first browse to https://radarr.example.com, Traefik forwards your session to traefik-forward-auth, to be authenticated. Traefik-forward-auth redirects you to your OIDC provider’s login (KeyCloak, in this case), but instructs the OIDC provider to redirect a successfully authenticated session back to https://auth.example.com/_oauth, rather than to https://radarr.example.com/_oauth.
When you successfully authenticate against the OIDC provider, you are redirected to the “redirect_uri” of https://auth.example.com. Again, your request hits Traefik, whichforwards the session to traefik-forward-auth, which knows that you’ve just been authenticated (cookies have a role to play here). Traefik-forward-auth also knows the URL of your original request (thanks to the X-Forwarded-Whatever header). Traefik-forward-auth redirects you to your original destination, and everybody is happy.
This clever workaround only works under 2 conditions:
- Your “auth host” has the same domain name as the hosts you’re protecting (i.e., auth.example.com protecting radarr.example.com)
- You explictly tell traefik-forward-auth to use a cookie authenticating your whole domain (i.e. example.com)
Setup environment
Create /var/data/config/traefik/traefik-forward-auth.env
as follows (change “master” if you created a different realm):
Prepare the docker service config
This is a small container, you can simply add the following content to the existing traefik-app.yml
deployed in the previous Traefik recipe:
If you’re not confident that forward authentication is working, add a simple “whoami” test container, to help debug traefik forward auth, before attempting to add it to a more complex container.
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
13.3 Serving
Launch
Redeploy traefik with docker stack deploy traefik-app -c /var/data/traefik/traeifk-app.yml
, to launch the traefik-forward-auth container.
Test
Browse to https://whoami.example.com (obviously, customized for your domain and having created a DNS record), and all going according to plan, you’ll be redirected to a KeyCloak login. Once successfully logged in, you’ll be directed to the basic whoami page.
Protect services
To protect any other service, ensure the service itself is exposed by Traefik (if you were previously using an oauth_proxy for this, you may have to migrate some labels from the oauth_proxy serivce to the service itself). Add the following 3 labels:
And re-deploy your services :)
13.4 Summary
What have we achieved? By adding an additional three simple labels to any service, we can secure any service behind our KeyCloak OIDC provider, with minimal processing / handling overhead.
Created:* [X] Traefik-forward-auth configured to authenticate against KeyCloak
13.5 Chef’s Notes
- KeyCloak is very powerful. You can add 2FA and all other clever things outside of the scope of this simple recipe ;)
14 Create registry mirror
Although we now have shared storage for our persistent container data, our docker nodes don’t share any other docker data, such as container images. This results in an inefficiency - every node which participates in the swarm will, at some point, need the docker image for every container deployed in the swarm.
When dealing with large container (looking at you, GitLab!), this can result in several gigabytes of wasted bandwidth per-node, and long delays when restarting containers on an alternate node. (It also wastes disk space on each node, but we’ll get to that in the next section)
The solution is to run an official Docker registry container as a “pull-through” cache, or “registry mirror”. By using our persistent storage for the registry cache, we can ensure we have a single copy of all the containers we’ve pulled at least once. After the first pull, any subsequent pulls from our nodes will use the cached version from our registry mirror. As a result, services are available more quickly when restarting container nodes, and we can be more aggressive about cleaning up unused containers on our nodes (more later)
The registry mirror runs as a swarm stack, using a simple docker-compose.yml. Customize your mirror FQDN below, so that Traefik will generate the appropriate LetsEncrypt certificates for it, and make it available via HTTPS.
14.1 Ingredients
- Docker swarm cluster with persistent shared storage
- Traefik configured per design
- DNS entry for the hostname you intend to use, pointed to your keepalived IP
14.2 Preparation
Create /var/data/config/registry/registry.yml as follows:
We create this registry without consideration for SSL, which will fail if we attempt to use the registry directly. However, we’re going to use the HTTPS-proxied version via Traefik, leveraging Traefik to manage the LetsEncrypt certificates required.Create /var/data/registry/registry-mirror-config.yml as follows:
14.3 Serving
Launch registry stack
Launch the registry stack by running docker stack deploy registry -c <path-to-docker-compose.yml>
Enable registry mirror and experimental features
To tell docker to use the registry mirror, and (while we’re here) in order to be able to watch the logs of any service from any manager node (an experimental feature in the current Atomic docker build), edit /etc/docker-latest/daemon.json on each node, and change from:
To:
Then restart docker by running:
Note the extra comma required after “false” above14.4 Chef’s notes
II Chef’s Favorites (Docker)
The following recipes are the chef’s current favorites - these are recipes actively in use and updated by @funkypenguin
hero: AutoPirate - A fully-featured recipe to automate finding, downloading, and organising your media
15 AutoPirate
Once the cutting edge of the “internet” (pre-world-wide-web and mosiac days), Usenet is now a murky, geeky alternative to torrents for file-sharing. However, it’s cool geeky, especially if you’re into having a fully automated media platform.
A good starter for the usenet scene is https://www.reddit.com/r/usenet/. Because it’s so damn complicated, a host of automated tools exist to automate the process of finding, downloading, and managing content. The tools included in this recipe are as follows:
This recipe presents a method to combine these tools into a single swarm deployment, and make them available securely.
15.1 Menu
Tools included in the AutoPirate stack are:
- SABnzbd : downloads data from usenet servers based on .nzb definitions
- NZBGet : downloads data from usenet servers based on .nzb definitions, but written in C++ and designed with performance in mind to achieve maximum download speed by using very little system resources (this is a popular alternative to SABnzbd)
- RTorrent is a CLI-based torrent client, which when combined with ruTorrent becomes a powerful and fully browser-managed torrent client. (Yes, it’s not Usenet, but Sonarr/Radarr will let fulfill your watchlist using either Usenet or torrents, so it’s worth including)
- NZBHydra : acts as a “meta-indexer”, so that your downloading tools (radarr, sonarr, etc) only need to be setup for a single indexes. Also produces interesting stats on indexers, which helps when evaluating which indexers are performing well.
- NZBHydra2 : is a high-performance rewrite of the original NZBHydra, with extra features. While still in beta, this NZBHydra2 will eventually supercede NZBHydra
- Sonarr : finds, downloads and manages TV shows
- Radarr : finds, downloads and manages movies
- Mylar : finds, downloads and manages comic books
- Headphones : finds, downloads and manages music
- Lazy Librarian : finds, downloads and manages ebooks
- Ombi : provides an interface to request additions to a Plex/Emby library using the above tools
- Jackett : Provides an local, caching, API-based interface to torrent trackers, simplifying the way your tools search for torrents.
Since this recipe is so long, and so many of the tools are optional to the final result (i.e., if you’re not interested in comics, you won’t want Mylar), I’ve described each individual tool on its own sub-recipe page (below), even though most of them are deployed very similarly.
15.2 Ingredients
- Docker swarm cluster with persistent shared storage
- Traefik configured per design
- Access to NZB indexers and Usenet servers
- DNS entries configured for each of the NZB tools in this recipe that you want to use
15.3 Preparation
Setup data locations
We’ll need a unique directories for each tool in the stack, bind-mounted into our containers, so create them upfront, in /var/data/autopirate:
Create a directory for the storage of your downloaded media, i.e., something like:
Create a user to “own” the above directories, and note the uid and gid of the created user. You’ll need to specify the UID/GID in the environment variables passed to the container (in the example below, I used 4242 - twice the meaning of life).
Secure public access
What you’ll quickly notice about this recipe is that every web interface is protected by an OAuth proxy.
Why? Because these tools are developed by a handful of volunteer developers who are focused on adding features, not necessarily implementing robust security. Most users wouldn’t expose these tools directly to the internet, so the tools have rudimentary (if any) access control.
To mitigate the risk associated with public exposure of these tools (you’re on your smartphone and you want to add a movie to your watchlist, what do you do, hotshot?), in order to gain access to each tool you’ll first need to authenticate against your given OAuth provider.
This is tedious, but you only have to do it once. Each tool (Sonarr, Radarr, etc) to be protected by an OAuth proxy, requires unique configuration. I use github to provide my oauth, giving each tool a unique logo while I’m at it (make up your own random string for OAUTH2PROXYCOOKIE_SECRET)
For each tool, create /var/data/autopirate/<tool>.env, and set the following:
Create at least /var/data/autopirate/authenticated-emails.txt, containing at least your own email address with your OAuth provider. If you wanted to grant access to a specific tool to other users, you’d need a unique authenticated-emails-<tool>.txt which included both normal email address as well as any addresses to be granted tool-specific access.
Setup components
Stack basics
Start with a swarm config file in docker-compose syntax, like this:
And end with a stanza like this:
Setup unique static subnets for every stack you deploy. This avoids IP/gateway conflicts which can otherwise occur when you’re creating/removing stacks a lot. See my list here.Assemble the tools..
Now work your way through the list of tools below, adding whichever tools your want to use, and finishing with the end section:
- SABnzbd
- NZBGet
- RTorrent
- Sonarr
- Radarr
- Mylar
- Lazy Librarian
- Headphones
- NZBHydra
- NZBHydra2
- Ombi
- Jackett
- End (launch the stack)
16 SABnzbd
16.1 Introduction
SABnzbd is the workhorse of the stack. It takes .nzb files as input (manually or from other autopirate stack tools), then connects to your chosen Usenet provider, downloads all the individual binaries referenced by the .nzb, and then tests/repairs/combines/uncompresses them all into the final result - media files.
16.2 Inclusion into AutoPirate
To include SABnzbd in your AutoPirate stack
(The only reason you wouldn’t use SABnzbd, would be if you were using NZBGet instead), include the following in your autopirate.yml stack definition file:
git pull
and a docker stack deploy
(Updated 10 June 2018) : In SABnzbd 2.3.3, hostname verification was added as a mandatory check. SABnzbd will refuse inbound connections which weren’t addressed to its own (initially, autodetected) hostname. This presents a problem within Docker Swarm, where container hostnames are random and disposable.
You’ll need to edit sabnzbd.ini (only created after your first launch), and replace the value in
host_whitelist
configuration (it’s comma-separated) with the name of your service within the swarm definition, as well as your FQDN as accessed via traefik.For example, mine simply reads
host_whitelist = sabnzbd.funkypenguin.co.nz, sabnzbd
16.3 Assemble more tools..
Continue through the list of tools below, adding whichever tools your want to use, and finishing with the end section:
- SABnzbd (this page)
- NZBGet
- RTorrent
- Sonarr
- Radarr
- Mylar
- Lazy Librarian
- Headphones
- Lidarr
- NZBHydra
- NZBHydra2
- Ombi
- Jackett
- Heimdall
- End (launch the stack)
16.4 Chef’s Notes
- In many cases, tools will integrate with each other. I.e., Radarr needs to talk to SABnzbd and NZBHydra, Ombi needs to talk to Radarr, etc. Since each tool runs within the stack under its own name, just refer to each tool by name (i.e. “radarr”), and docker swarm will resolve the name to the appropriate container. You can identify the tool-specific port by looking at the docker-compose service definition.
17 NZBGet
17.1 Introduction
NZBGet performs the same function as SABnzbd (downloading content from Usenet servers), but it’s lightweight and fast(er), written in C++ (as opposed to Python).
17.2 Inclusion into AutoPirate
To include NZBGet in your AutoPirate stack
(The only reason you wouldn’t use NZBGet, would be if you were using SABnzbd instead), include the following in your autopirate.yml stack definition file:
git pull
and a docker stack deploy
NZBGet uses a 401 header to prompt for authentication. When you use OAuth2_proxy, this seems to break. Since we trust OAuth to authenticate us, we can just disable NZGet’s own authentication, by changing ControlPassword to null in nzbget.conf (i.e.
ControlPassword=
)17.3 Assemble more tools..
Continue through the list of tools below, adding whichever tools your want to use, and finishing with the end section:
- SABnzbd
- NZBGet (this page)
- RTorrent
- Sonarr
- Radarr
- Mylar
- Lazy Librarian
- Headphones
- Lidarr
- NZBHydra
- NZBHydra2
- Ombi
- Jackett
- Heimdall
- End (launch the stack)
17.4 Chef’s Notes
- In many cases, tools will integrate with each other. I.e., Radarr needs to talk to SABnzbd and NZBHydra, Ombi needs to talk to Radarr, etc. Since each tool runs within the stack under its own name, just refer to each tool by name (i.e. “radarr”), and docker swarm will resolve the name to the appropriate container. You can identify the tool-specific port by looking at the docker-compose service definition.
18 RTorrent / ruTorrent
RTorrent is a popular CLI-based bittorrent client, and ruTorrent is a powerful web interface for rtorrent.
18.1 Choose incoming port
When using a torrent client from behind NAT (which swarm, by nature, is), you typically need to set a static port for inbound torrent communications. In the example below, I’ve set the port to 36258. You’ll need to configure /var/data/autopirate/rtorrent/rtorrent/rtorrent.rc with the equivalent port.
18.2 Inclusion into AutoPirate
To include ruTorrent in your AutoPirate stack, include the following in your autopirate.yml stack definition file:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
18.3 Assemble more tools..
Continue through the list of tools below, adding whichever tools your want to use, and finishing with the end section:
- SABnzbd
- NZBGet
- RTorrent (this page)
- Sonarr
- Radarr
- Mylar
- Lazy Librarian
- Headphones
- Lidarr
- NZBHydra
- NZBHydra2
- Ombi
- Jackett
- Heimdall
- End (launch the stack)
18.4 Chef’s Notes
- In many cases, tools will integrate with each other. I.e., Radarr needs to talk to SABnzbd and NZBHydra, Ombi needs to talk to Radarr, etc. Since each tool runs within the stack under its own name, just refer to each tool by name (i.e. “radarr”), and docker swarm will resolve the name to the appropriate container. You can identify the tool-specific port by looking at the docker-compose service definition.
19 Sonarr
Sonarr is a tool for finding, downloading and managing your TV series.
19.1 Inclusion into AutoPirate
To include Sonarr in your AutoPirate stack, include the following in your autopirate.yml stack definition file:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
19.2 Assemble more tools..
Continue through the list of tools below, adding whichever tools your want to use, and finishing with the end section:
- SABnzbd
- NZBGet
- RTorrent
- Sonarr (this page)
- Radarr
- Mylar
- Lazy Librarian
- Headphones
- Lidarr
- NZBHydra
- NZBHydra2
- Ombi
- Jackett
- Heimdall
- End (launch the stack)
19.3 Chef’s Notes
- In many cases, tools will integrate with each other. I.e., Radarr needs to talk to SABnzbd and NZBHydra, Ombi needs to talk to Radarr, etc. Since each tool runs within the stack under its own name, just refer to each tool by name (i.e. “radarr”), and docker swarm will resolve the name to the appropriate container. You can identify the tool-specific port by looking at the docker-compose service definition.
20 Radarr
Radarr is a tool for finding, downloading and managing movies. Features include:
- Adding new movies with lots of information, such as trailers, ratings, etc.
- Can watch for better quality of the movies you have and do an automatic upgrade. eg. from DVD to Blu-Ray
- Automatic failed download handling will try another release if one fails
- Manual search so you can pick any release or to see why a release was not downloaded automatically
- Full integration with SABnzbd and NZBGet
- Automatically searching for releases as well as RSS Sync
- Automatically importing downloaded movies
- Recognizing Special Editions, Director’s Cut, etc.
- Identifying releases with hardcoded subs
- Importing movies from various online sources, such as IMDb Watchlists (A complete list can be found here)
- Full integration with Kodi, Plex (notification, library update)
- And a beautiful UI
- Importing Metadata such as trailers or subtitles
20.1 Inclusion into AutoPirate
To include Radarr in your AutoPirate stack, include the following in your autopirate.yml stack definition file:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
20.2 Assemble more tools..
Continue through the list of tools below, adding whichever tools your want to use, and finishing with the end section:
- SABnzbd
- NZBGet
- RTorrent
- Sonarr
- Radarr (this page)
- Mylar
- Lazy Librarian
- Headphones
- Lidarr
- NZBHydra
- NZBHydra2
- Ombi
- Jackett
- Heimdall
- End (launch the stack)
20.3 Chef’s Notes
- In many cases, tools will integrate with each other. I.e., Radarr needs to talk to SABnzbd and NZBHydra, Ombi needs to talk to Radarr, etc. Since each tool runs within the stack under its own name, just refer to each tool by name (i.e. “radarr”), and docker swarm will resolve the name to the appropriate container. You can identify the tool-specific port by looking at the docker-compose service definition.
21 Mylar
Mylar is a tool for downloading and managing digital comic books.
21.1 Inclusion into AutoPirate
To include Mylar in your AutoPirate stack, include the following in your autopirate.yml stack definition file:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
21.2 Assemble more tools..
Continue through the list of tools below, adding whichever tools your want to use, and finishing with the end section:
- SABnzbd
- NZBGet
- RTorrent
- Sonarr
- Radarr
- Mylar (this page)
- Lazy Librarian
- Headphones
- Lidarr
- NZBHydra
- NZBHydra2
- Ombi
- Jackett
- Heimdall
- End (launch the stack)
21.3 Chef’s Notes
- In many cases, tools will integrate with each other. I.e., Radarr needs to talk to SABnzbd and NZBHydra, Ombi needs to talk to Radarr, etc. Since each tool runs within the stack under its own name, just refer to each tool by name (i.e. “radarr”), and docker swarm will resolve the name to the appropriate container. You can identify the tool-specific port by looking at the docker-compose service definition.
- If you intend to configure Mylar to perform its own NZB searches and push the hits to a downloader such as SABnzbd, then in addition to configuring the connection to SAB with host, port and api key, you will need to set the parameter
host_return
parameter to the fully qualified Mylar address (e.g.http://mylar:8090
).This will provide the link to the downloader necessary to initiate the download. This parameter is not presented in the user interface so the config file (
$MYLAR_HOME/config.ini
) will need to be manually updated. The parameter can be found under the [Interface] section of the file. (Details)
22 LazyLibrarian
LazyLibrarian is a tool to follow authors and grab metadata for all your digital reading needs. It uses a combination of Goodreads Librarything and optionally GoogleBooks as sources for author info and book info. Features include:
- Find authors and add them to the database
- List all books of an author and mark ebooks or audiobooks as ‘wanted’.
- When processing the downloaded books it will save a cover picture (if available) and save all metadata into metadata.opf next to the bookfile (calibre compatible format)
- AutoAdd feature for book management tools like Calibre which must have books in flattened directory structure, or use calibre to import your books into an existing calibre library
- LazyLibrarian can also be used to search for and download magazines, and monitor for new issues
22.1 Inclusion into AutoPirate
To include LazyLibrarian in your AutoPirate stack, include the following in your autopirate.yml stack definition file:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
22.2 Assemble more tools..
Continue through the list of tools below, adding whichever tools your want to use, and finishing with the end section:
- SABnzbd
- NZBGet
- RTorrent
- Sonarr
- Radarr
- Mylar
- Lazy Librarian (this page)
- Headphones
- Lidarr
- NZBHydra
- NZBHydra2
- Ombi
- Jackett
- Heimdall
- End (launch the stack)
22.3 Chef’s Notes
- The calibre-server container co-exists within the Lazy Librarian (LL) containers so that LL can automatically add a book to Calibre using the calibre-server interface. The calibre library can then be properly viewed using the calibre-web recipe.
- In many cases, tools will integrate with each other. I.e., Radarr needs to talk to SABnzbd and NZBHydra, Ombi needs to talk to Radarr, etc. Since each tool runs within the stack under its own name, just refer to each tool by name (i.e. “radarr”), and docker swarm will resolve the name to the appropriate container. You can identify the tool-specific port by looking at the docker-compose service definition.
hero: AutoPirate - A fully-featured recipe to automate finding, downloading, and organising your media
This is not a complete recipe - it’s a component of the autopirate “uber-recipe”, but has been split into its own page to reduce complexity.23 Headphones
Headphones is an automated music downloader for NZB and Torrent, written in Python. It supports SABnzbd, NZBget, Transmission, Torrent, Deluge and Blackhole.
23.1 Inclusion into AutoPirate
To include Headphones in your AutoPirate stack, include the following in your autopirate.yml stack definition file:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
23.2 Assemble more tools..
Continue through the list of tools below, adding whichever tools your want to use, and finishing with the end section:
- SABnzbd
- NZBGet
- RTorrent
- Sonarr
- Radarr
- Mylar
- Lazy Librarian
- Headphones (this page)
- Lidarr
- NZBHydra
- NZBHydra2
- Ombi
- Jackett
- Heimdall
- End (launch the stack)
23.3 Chef’s Notes
- In many cases, tools will integrate with each other. I.e., Radarr needs to talk to SABnzbd and NZBHydra, Ombi needs to talk to Radarr, etc. Since each tool runs within the stack under its own name, just refer to each tool by name (i.e. “radarr”), and docker swarm will resolve the name to the appropriate container. You can identify the tool-specific port by looking at the docker-compose service definition.
hero: AutoPirate - A fully-featured recipe to automate finding, downloading, and organising your media
This is not a complete recipe - it’s a component of the autopirate “uber-recipe”, but has been split into its own page to reduce complexity.24 Lidarr
Lidarr is an automated music downloader for NZB and Torrent. It performs the same function as Headphones, but is written using the same(ish) codebase as Radarr and Sonarr. It’s blazingly fast, and includes beautiful album/artist art. Lidarr supports SABnzbd, NZBGet, Transmission, Torrent, Deluge and Blackhole (just like Sonarr / Radarr)
24.1 Inclusion into AutoPirate
To include Lidarr in your AutoPirate stack, include the following in your autopirate.yml stack definition file:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
24.2 Assemble more tools..
Continue through the list of tools below, adding whichever tools your want to use, and finishing with the end section:
- SABnzbd
- NZBGet
- RTorrent
- Sonarr
- Radarr
- Mylar
- Lazy Librarian
- Headphones
- Lidarr (this page)
- NZBHydra
- NZBHydra
- NZBHydra2
- Ombi
- Jackett
- Heimdall
- End (launch the stack)
24.3 Chef’s Notes
- In many cases, tools will integrate with each other. I.e., Radarr needs to talk to SABnzbd and NZBHydra, Ombi needs to talk to Radarr, etc. Since each tool runs within the stack under its own name, just refer to each tool by name (i.e. “radarr”), and docker swarm will resolve the name to the appropriate container. You can identify the tool-specific port by looking at the docker-compose service definition.
- The addition of the Lidarr recipe was contributed by our very own @gpulido in Discord (http://chat.funkypenguin.co.nz) - Thanks Gabriel!
25 NZBHydra
NZBHydra is a meta search for NZB indexers. It provides easy access to a number of raw and newznab based indexers. You can search all your indexers from one place and use it as indexer source for tools like Sonarr or CouchPotato. Features include:
- Search by IMDB, TMDB, TVDB, TVRage and TVMaze ID (including season and episode) and filter by age and size. If an ID is not supported by an indexer it is attempted to be converted (e.g. TMDB to IMDB)
- Query generation, meaning when you search for a movie using e.g. an IMDB ID a query will be generated for raw indexers. Searching for a series season 1 episode 2 will also generate queries for raw indexers, like s01e02 and 1x02
- Grouping of results with the same title and of duplicate results, accounting for result posting time, size, group and poster. By default only one of the duplicates is shown. You can provide an indexer score to influence which one that might be
- Compatible with Sonarr, CP, NZB 360, SickBeard, Mylar and Lazy Librarian (and others)
- Statistics on indexers (average response time, share of results, access errors), searches and downloads per time of day and day of week, NZB download history and search history (both via internal GUI and API)
25.1 Inclusion into AutoPirate
To include NZBHydra in your AutoPirate stack, include the following in your autopirate.yml stack definition file:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
25.2 Assemble more tools..
Continue through the list of tools below, adding whichever tools your want to use, and finishing with the end section:
- SABnzbd
- NZBGet
- RTorrent
- Sonarr
- Radarr
- Mylar
- Lazy Librarian
- Headphones
- Lidarr
- NZBHydra (this page)
- NZBHydra2
- Ombi
- Jackett
- Heimdall
- End (launch the stack)
25.3 Chef’s Notes
- In many cases, tools will integrate with each other. I.e., Radarr needs to talk to SABnzbd and NZBHydra, Ombi needs to talk to Radarr, etc. Since each tool runs within the stack under its own name, just refer to each tool by name (i.e. “radarr”), and docker swarm will resolve the name to the appropriate container. You can identify the tool-specific port by looking at the docker-compose service definition.
26 NZBHydra 2
NZBHydra 2 is a meta search for NZB indexers. It provides easy access to a number of raw and newznab based indexers. You can search all your indexers from one place and use it as an indexer source for tools like Sonarr, Radarr or CouchPotato.
NZBHydra 2 is a complete rewrite of NZBHydra (1). It’s currently in Beta. It works mostly fine but some functions might not be completely done and incompatibilities with some tools might still exist. You might want to run both in parallel for migration / testing purposes, but ultimately you’ll probably want to switch over to NZBHydra 2 exclusively.Features include:
- Searches Anizb, BinSearch, NZBIndex and any newznab compatible indexers. Merges all results, filters them by a number of configurable restrictions, recognizes duplicates and returns them all in one place
- Add results to NZBGet or SABnzbd
- Support for all relevant media IDs (IMDB, TMDB, TVDB, TVRage, TVMaze) and conversion between them
- Query generation, meaning a query will be generated if only a media ID is provided in the search and the indexer doesn’t support the ID or if no results were found
- Compatible with Sonarr, Radarr, NZBGet, SABnzbd, nzb360, CouchPotato, Mylar, Lazy Librarian, Sick Beard, Jackett/Cardigann, Watcher, etc.
- Search and download history and extensive stats. E.g. indexer response times, download shares, NZB age, etc.
- Authentication and multi-user support
- Automatic update of NZB download status by querying configured downloaders
- RSS support with configurable cache times
- Torrent support (Although I prefer Jackett for this):
* For GUI searches, allowing you to download torrents to a blackhole folder
* A separate Torznab compatible endpoint for API requests, allowing you to merge multiple trackers - Extensive configurability
- Migration of database and settings from v1
26.1 Inclusion into AutoPirate
To include NZBHydra2 in your AutoPirate stack, include the following in your autopirate.yml stack definition file:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
26.2 Assemble more tools..
Continue through the list of tools below, adding whichever tools your want to use, and finishing with the end section:
- SABnzbd
- NZBGet
- RTorrent
- Sonarr
- Radarr
- Mylar
- Lazy Librarian
- Headphones
- Lidarr
- NZBHydra
- NZBHydra2 (this page)
- Ombi
- Jackett
- Heimdall
- End (launch the stack)
26.3 Chef’s Notes
- In many cases, tools will integrate with each other. I.e., Radarr needs to talk to SABnzbd and NZBHydra2, Ombi needs to talk to Radarr, etc. Since each tool runs within the stack under its own name, just refer to each tool by name (i.e. “radarr”), and docker swarm will resolve the name to the appropriate container. You can identify the tool-specific port by looking at the docker-compose service definition.
- Note that NZBHydra2 can co-exist with NZBHydra (1), but if you want your tools (Sonarr, Radarr, etc) to use NZBHydra2, you’ll need to change both the target hostname (to “hydra2”) and the target port (to 5076).
27 Ombi
Ombi is a useful addition to the autopirate stack. Features include:
- Lets users request Movies and TV Shows (whether it being the entire series, an entire season, or even single episodes.)
- Easily manage your requests
User management system (supports plex.tv, Emby and local accounts)
- A landing page that will give you the availability of your Plex/Emby server and also add custom notification text to inform your users of downtime.
- Allows your users to get custom notifications!
- Will show if the request is already on plex or even if it’s already monitored.
Automatically updates the status of requests when they are available on Plex/Emby
27.1 Inclusion into AutoPirate
To include Ombi in your AutoPirate stack, include the following in your autopirate.yml stack definition file:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
27.2 Assemble more tools..
Continue through the list of tools below, adding whichever tools your want to use, and finishing with the end section:
- SABnzbd
- NZBGet
- RTorrent
- Sonarr
- Radarr
- Mylar
- Lazy Librarian
- Headphones
- Lidarr
- NZBHydra
- NZBHydra2
- Ombi (this page)
- Jackett
- Heimdall
- End (launch the stack)
27.3 Chef’s Notes
- In many cases, tools will integrate with each other. I.e., Radarr needs to talk to SABnzbd and NZBHydra, Ombi needs to talk to Radarr, etc. Since each tool runs within the stack under its own name, just refer to each tool by name (i.e. “radarr”), and docker swarm will resolve the name to the appropriate container. You can identify the tool-specific port by looking at the docker-compose service definition.
28 Jackett
Jackett works as a proxy server: it translates queries from apps (Sonarr, Radarr, Mylar, etc) into tracker-site-specific http queries, parses the html response, then sends results back to the requesting software.
This allows for getting recent uploads (like RSS) and performing searches. Jackett is a single repository of maintained indexer scraping & translation logic - removing the burden from other apps.
28.1 Inclusion into AutoPirate
To include Jackett in your AutoPirate stack, include the following in your autopirate.yml stack definition file:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
28.2 Assemble more tools..
Continue through the list of tools below, adding whichever tools your want to use, and finishing with the end section:
- SABnzbd
- NZBGet
- RTorrent
- Sonarr
- Radarr
- Mylar
- Lazy Librarian
- Headphones
- Lidarr
- NZBHydra
- NZBHydra2
- Ombi
- Jackett (this page)
- Heimdall
- End (launch the stack)
28.3 Chef’s Notes
- In many cases, tools will integrate with each other. I.e., Radarr needs to talk to SABnzbd and NZBHydra, Ombi needs to talk to Radarr, etc. Since each tool runs within the stack under its own name, just refer to each tool by name (i.e. “radarr”), and docker swarm will resolve the name to the appropriate container. You can identify the tool-specific port by looking at the docker-compose service definition.
29 Heimdall
Heimdall Application Dashboard is a dashboard for all your web applications. It doesn’t need to be limited to applications though, you can add links to anything you like.
Heimdall is an elegant solution to organise all your web applications. Its dedicated to this purpose so you wont lose your links in a sea of bookmarks.
Heimdall provides a single URL to manage access to all of your autopirate tools, and includes “enhanced” (i.e., display stats within Heimdall without launching the app) access to NZBGet, SABnzbd, and friends.
29.1 Inclusion into AutoPirate
To include Heimdall in your AutoPirate stack, include the following in your autopirate.yml stack definition file:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
29.2 Assemble more tools..
Continue through the list of tools below, adding whichever tools your want to use, and finishing with the end section:
- SABnzbd
- NZBGet
- RTorrent
- Sonarr
- Radarr
- Mylar
- Lazy Librarian
- Headphones
- Lidarr
- NZBHydra
- NZBHydra2
- Ombi
- Jackett
- Heimdall (this page)
- End (launch the stack)
29.3 Chef’s Notes
- In many cases, tools will integrate with each other. I.e., Radarr needs to talk to SABnzbd and NZBHydra, Ombi needs to talk to Radarr, etc. Since each tool runs within the stack under its own name, just refer to each tool by name (i.e. “radarr”), and docker swarm will resolve the name to the appropriate container. You can identify the tool-specific port by looking at the docker-compose service definition.
- The inclusion of Heimdall was due to the efforts of @gkoerk in our Discord server. Thanks gkoerk!
Launch Autopirate stack
Launch the AutoPirate stack by running docker stack deploy autopirate -c <path -to-docker-compose.yml>
Confirm the container status by running “docker stack ps autopirate”, and wait for all containers to enter the “Running” state.
Log into each of your new tools at its respective HTTPS URL. You’ll be prompted to authenticate against your OAuth provider, and upon success, redirected to the tool’s UI.
29.4 Chef’s Notes
- This is a complex stack. Sing out in the comments if you found a flaw or need a hand :)
hero: Duplicity - A boring recipe to backup your exciting stuff. Boring is good.
30 Duplicity
Intro
Duplicity backs directories by producing encrypted tar-format volumes and uploading them to a remote or local file server. Because duplicity uses librsync, the incremental archives are space efficient and only record the parts of files that have changed since the last backup. Because duplicity uses GnuPG to encrypt and/or sign these archives, they will be safe from spying and/or modification by the server.
So what does this mean for our stack? It means we can leverage Duplicity to backup all our data-at-rest to a wide variety of cloud providers, including, but not limited to:
- acd_cli
- Amazon S3
- Backblaze B2
- DropBox
- ftp
- Google Docs
- Google Drive
- Microsoft Azure
- Microsoft Onedrive
- Rackspace Cloudfiles
- rsync
- ssh/scp
- SwiftStack
30.1 Ingredients
- Docker swarm cluster with persistent shared storage
- Credentials for one of the Duplicity’s supported upload destinations
30.2 Preparation
Setup data locations
We’ll need a folder to store a docker-compose .yml file, and an associated .env file. If you’re following my filesystem layout, create /var/data/config/duplicity (for the config), and /var/data/duplicity (for the metadata) as follows:
(Optional) Create Google Cloud Storage bucket
I didn’t already have an archival/backup provider, so I chose Google Cloud “cloud” storage for the low price-point - 0.7 cents per GB/month (Plus you start with $300 credit even when signing up for the free tier). You can use any destination supported by Duplicity’s URL scheme though, just make sure you specify the necessary environment variables.
- Sign up, create an empty project, enable billing, and create a bucket. Give your bucket a unique name, example “jack-and-jills-bucket” (it’s unique across the entire Google Cloud)
- Under “Storage” section > “Settings” > “Interoperability” tab > click “Enable interoperable access” and then “Create a new key” button and note both Access Key and Secret.
Prepare environment
- Generate a random passphrase to use to encrypt your data. Save this somewhere safe, without it you won’t be able to restore!
- Seriously, save. it. somewhere. safe.
- Create duplicity.env, and populate with the following variables
Run a test backup
Before we launch the automated daily backups, let’s run a test backup, as follows:
You should see some activity, with a summary of bytes transferred at the end.
Run a test restore
Repeat after me: “If you don’t verify your backup, it’s not a backup”.
Depending on what tier of storage you chose from your provider (i.e., Google Coldline, or Amazon S3), you may be charged for downloading data.Run a variation of the following to confirm a file you expect to be backed up, is backed up. (I used traefik.yml from the traefik recipie, since this is likely to exist for every reader).
Once you’ve identified a file to test-restore, use a variation of the following to restore it to /tmp (from the perspective of the container - it’s actually /var/data/duplicity/tmp)
Examine the contents of /var/data/duplicity/tmp/traefik-restored.yml to confirm it contains valid data.
Setup Docker Swarm
Now that we have confidence in our backup/restore process, let’s automate it by creating a docker swarm config file in docker-compose syntax (v3), something like this:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
Setup unique static subnets for every stack you deploy. This avoids IP/gateway conflicts which can otherwise occur when you’re creating/removing stacks a lot. See my list here.
30.3 Serving
Launch Duplicity stack
Launch Duplicity stack by running docker stack deploy duplicity -c <path -to-docker-compose.yml>
Nothing will happen. Very boring. But when the cron script fires (daily), duplicity will do its thing, and backup everything in /var/data to your cloud destination.
30.4 Chef’s Notes
- Automatic backup can still fail if nobody checks that it’s running successfully. I’ll be working on an upcoming recipe to monitor the elements of the stack, including the success/failure of duplicity jobs.
- The container provides the facility to specify an SMTP host and port, but not credentials, which makes it close to useless. As a result, I’ve left SMTP out of this recipe. To enable email notifications (if your SMTP server doesn’t require auth), add
SMTP_HOST
,SMTP_PORT
,EMAIL_FROM
andEMAIL_TO
variables to duplicity.env
hero: Real heroes backup their
31 Elkar Backup
Don’t be like Cameron. Backup your stuff.
<iframe width=”560” height=”315” src=”https://www.youtube.com/embed/1UtFeMoqVHQ” frameborder=”0” allow=”accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture” allowfullscreen></iframe>
Ongoing development of this recipe is sponsored by The Common Observatory. Thanks guys![
ElkarBackup is a free open-source backup solution based on RSync/RSnapshot. It’s basically a web wrapper around rsync/rsnapshot, which means that your backups are just files on a filesystem, utilising hardlinks for tracking incremental changes. I find this result more reassuring than a blob of compressed, (encrypted?) data that more sophisticated backup solutions would produce for you.
31.1 Details
31.2 Ingredients
- Docker swarm cluster with persistent shared storage
- Traefik configured per design
- DNS entry for the hostname you intend to use, pointed to your keepalived IP
31.3 Preparation
Setup data locations
We’ll need several directories to bind-mount into our container, so create them in /var/data/elkarbackup:
Prepare environment
Create /var/data/config/elkarbackup/elkarbackup.env, and populate with the following variables
Create /var/data/config/elkarbackup/elkarbackup-db-backup.env
, and populate with the following, to setup the nightly database dump.
Also, did you ever hear about the guy who said “_I wish I had fewer backups”?
No, me either :shrug:
Setup Docker Swarm
Create a docker swarm config file in docker-compose syntax (v3), something like this:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
Setup unique static subnets for every stack you deploy. This avoids IP/gateway conflicts which can otherwise occur when you’re creating/removing stacks a lot. See my list here.
31.4 Serving
Launch ElkarBackup stack
Launch the ElkarBackup stack by running docker stack deploy elkarbackup -c <path -to-docker-compose.yml>
Log into your new instance at https://YOUR-FQDN, with user “root” and the password default password “root”:
First thing you do, change your password, using the gear icon, and “Change Password” link:
Have a read of the Elkarbackup Docs - they introduce the concept of clients (hosts containing data to be backed up), jobs (what data gets backed up), policies (when is data backed up and how long is it kept).
At the very least, you want to setup a client called “localhost” with an empty path (i.e., the job path will be accessed locally, without SSH), and then add a job to this client to backup /var/data, excluding /var/data/runtime
and /var/data/elkarbackup/backup
(unless you like “backup-ception”)
Copying your backup data offsite
From the WebUI, you can download a script intended to be executed on a remote host, to backup your backup data to an offsite location. This is a Good Idea(tm), but needs some massaging for a Docker swarm deployment.
Here’s a variation to the standard script, which I’ve employed:
You’ll note that I don’t use the script to create a mysql dump (since Elkar is running within a container anyway), rather I just rely on the database dump which is made nightly into/var/data/elkarbackup/database-dump/
Restoring data
Repeat after me : “It’s not a backup unless you’ve tested a restore”
I had some difficulty making restoring work well in the webUI. My attempts to “Restore to client” failed with an SSH error about “localhost” not found. I was able to download the backup from my web browser, so I considered it a successful restore, since I can retrieve the backed-up data either from the webUI or from the filesystem directly.To restore files form a job, click on the “Restore” button in the WebUI, while on the Jobs tab:
This takes you to a list of backup names and file paths. You can choose to download the entire contents of the backup from your browser as a .tar.gz, or to restore the backup to the client. If you click on the name of the backup, you can also drill down into the file structure, choosing to restore a single file or directory.
Ongoing development of this recipe is sponsored by The Common Observatory. Thanks guys![
31.5 Chef’s Notes
- If you wanted to expose the ElkarBackup UI directly, you could remove the oauth2_proxy from the design, and move the traefik_public-related labels directly to the app service. You’d also need to add the traefik_public network to the app service.
- The original inclusion of ElkarBackup was due to the efforts of @gpulido in our Discord server. Thanks Gabriel!
32 Emby
Emby (think “M.B.” or “Media Browser”) is best described as “like Plex but different” - It’s a bit geekier and less polished than Plex, but it allows for more flexibility and customization.
I’ve started experimenting with Emby as an alternative to Plex, because of the advanced parental controls it offers. Based on my experimentation thus far, I have a “kid-safe” profile which automatically logs in, and only displays kid-safe content, based on ratings.
32.1 Ingredients
- Docker swarm cluster with persistent shared storage
- Traefik configured per design
- DNS entry for the hostname you intend to use, pointed to your keepalived IP
32.2 Preparation
Setup data locations
We’ll need a location to store Emby’s library data, config files, logs and temporary transcoding space, so create /var/data/emby, and make sure it’s owned by the user and group who also own your media data.
Prepare environment
Create emby.env, and populate with PUID/GUID for the user who owns the /var/data/emby directory (above) and your actual media content (in this example, the media content is at /srv/data)
Setup Docker Swarm
Create a docker swarm config file in docker-compose syntax (v3), something like this:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
Setup unique static subnets for every stack you deploy. This avoids IP/gateway conflicts which can otherwise occur when you’re creating/removing stacks a lot. See my list here.
32.3 Serving
Launch Emby stack
Launch the stack by running docker stack deploy emby -c <path -to-docker-compose.yml>
Log into your new instance at https://YOUR-FQDN, and complete the wizard-based setup to complete deploying your Emby.
32.4 Chef’s Notes
- I didn’t use an oauth2_proxy for this stack, because it would interfere with mobile client support.
- Got an NVIDIA GPU? See this blog post re how to use your GPU to transcode your media!
- We don’t bother exposing the HTTPS port for Emby, since Traefik is doing the SSL termination for us already.
33 Home Assistant
Home Assistant is a home automation platform written in Python, with extensive support for 3rd-party home-automation platforms including Xaomi, Phillips Hue, and a bazillion others.
This recipie combines the extensibility of Home Assistant with the flexibility of InfluxDB (for time series data store) and Grafana (for beautiful visualisation of that data).
33.1 Ingredients
- Docker swarm cluster with persistent shared storage
- Traefik configured per design
- DNS entry for the hostname you intend to use, pointed to your keepalived IP
33.2 Preparation
Setup data locations
We’ll need several directories to bind-mount into our container, so create them in /var/data/homeassistant:
Now create a directory for the influxdb realtime data:
Prepare environment
Create /var/data/config/homeassistant/grafana.env, and populate with the following - this is to enable grafana to work with oauth2_proxy without requiring an additional level of authentication:
Setup Docker Swarm
Create a docker swarm config file in docker-compose syntax (v3), something like this:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
Setup unique static subnets for every stack you deploy. This avoids IP/gateway conflicts which can otherwise occur when you’re creating/removing stacks a lot. See my list here.
33.3 Serving
Launch Home Assistant stack
Launch the Home Assistant stack by running docker stack deploy homeassistant -c <path -to-docker-compose.yml>
Log into your new instance at https://YOUR-FQDN, the password you created in configuration.yml as “frontend - api_key”. Then setup a bunch of sensors, and log into https://grafana.YOUR FQDN and create some beautiful graphs :)
33.4 Chef’s Notes
- I tried to protect Home Assistant using oauth2_proxy, but HA is incompatible with the websockets implementation used by Home Assistant. Until this can be fixed, I suggest that geeks set frontend: api_key to a long and complex string, and rely on this to prevent malevolent internet miscreants from turning their lights on at 2am!
34 iBeacons with Home assistant
This is not a complete recipe - it’s an optional additional of the HomeAssistant “recipe”, since it only applies to a subset of usersOne of the most useful features of Home Assistant is location awareness. I don’t care if someone opens my office door when I’m home, but you bet I care about (and want to be notified) it if I’m away!
34.1 Ingredients
1. HomeAssistant per recipe
2. iBeacon(s) - This recipe is for https://s.click.aliexpress.com/e/bzyLCnAp
34.2 Preparation
Write UUID to iBeacon
The iBeacons come with no UUID. We use the LightBlue Explorer app to pair with them (code is “123456”), and assign own own UUID.
Generate your own UUID, or get a random one at https://www.uuidgenerator.net/
Plug in your iBeacon, launch LightBlue Explorer, and find your iBeacon. The first time you attempt to interrogate it, you’ll be prompted to pair. Although it’s not recorded anywhere in the documentation (grr!), the pairing code is 123456
Having paired, you’ll be able to see the vital statistics of your iBeacon.
34.3 Chef’s Notes
hero: Huginn - A recipe for self-hosted, hackable version of IFFTT / Zapier
35 Huginn
Huginn is a system for building agents that perform automated tasks for you online. They can read the web, watch for events, and take actions on your behalf. Huginn’s Agents create and consume events, propagating them along a directed graph. Think of it as a hackable version of IFTTT or Zapier on your own server.
<iframe src=”https://player.vimeo.com/video/61976251” width=”640” height=”433” frameborder=”0” webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
35.1 Ingredients
- Docker swarm cluster with persistent shared storage
- Traefik configured per design
35.2 Preparation
Setup data locations
Create the location for the bind-mount of the database, so that it’s persistent:
Create email address
Strictly speaking, you don’t have to integrate Huginn with email. However, since we created our own mailserver stack earlier, it’s worth using it to enable emails within Huginn.
Prepare environment
Create /var/data/huginn/huginn.env, and populate with the following variables. Set the “INVITATION_CODE” variable if you want to require users to enter a code to sign up (protects the UI from abuse) (The full list of Huginn environment variables is available here)
Setup Docker Swarm
Create a docker swarm config file in docker-compose syntax (v3), something like this:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
Setup unique static subnets for every stack you deploy. This avoids IP/gateway conflicts which can otherwise occur when you’re creating/removing stacks a lot. See my list here.
35.3 Serving
Launch Huginn stack
Launch the Huginn stack by running docker stack deploy huginn -c <path -to-docker-compose.yml>
Log into your new instance at https://YOUR-FQDN. You’ll need to use the “Sign Up” button, and (optionally) enter your invitation code in order to create your account.
35.4 Chef’s Notes
1. I initially considered putting an oauth proxy in front of Huginn, but since the invitation code logic prevents untrusted access, and since using a proxy would break oauth for sevices like Twitter integration, I left it out.
hero: Kanboard - A recipe to get your personal kanban on
36 Kanboard
Kanboard is a Kanban tool, developed by Frdric Guillot. (Who also happens to be the developer of my favorite RSS reader, Miniflux)
Kanboard is one of my sponsored projects - a project I financially support on a regular basis because of its utility to me. I use it both in my DayJob(tm), and to manage my overflowing, overly-optimistic personal commitments!Features include:
- Visualize your work
- Limit your work in progress to be more efficient
- Customize your boards according to your business activities
- Multiple projects with the ability to drag and drop tasks
- Reports and analytics
- Fast and simple to use
- Access from anywhere with a modern browser
- Plugins and integrations with external services
- Free, open source and self-hosted
- Super simple installation
36.1 Ingredients
- Docker swarm cluster with persistent shared storage
- Traefik configured per design
- DNS entry pointing your NextCloud url (kanboard.example.com) to your keepalived IP
36.2 Preparation
Setup data locations
Create the location for the bind-mount of the application data, so that it’s persistent:
Setup Environment
If you intend to use an OAuth proxy to further secure public access to your instance, create a kanboard.env
file to hold your environment variables, and populate with your OAuth provider’s details (the cookie secret you can just make up):
Setup Docker Swarm
Create a docker swarm config file in docker-compose syntax (v3), something like this:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
36.3 Serving
Launch Kanboard stack
Launch the Kanboard stack by running docker stack deploy kanboard -c <path -to-docker-compose.yml>
Log into your new instance at https://YOUR-FQDN. Default credentials are admin/admin, after which you can change (under ‘profile’) and add more users.
36.4 Chef’s Notes
- The default theme can be significantly improved by applying the ThemePlus plugin.
- Kanboard becomes more useful when you integrate in/outbound email with MailGun, SendGrid, or Postmark.
hero: Miniflux - A recipe for a lightweight minimalist RSS reader
37 Miniflux
Miniflux is a lightweight RSS reader, developed by Frdric Guillot. (Who also happens to be the developer of the favorite Open Source Kanban app, Kanboard)
I’ve reviewed Miniflux in detail on my blog, but features (among many) that I appreciate:
- Compatible with the Fever API, read your feeds through existing mobile and desktop clients (This is the killer feature for me. I hardly ever read RSS on my desktop, I typically read on my iPhone or iPad, using Fiery Feeds or my new squeeze, Unread)
- Send your bookmarks to Pinboard, Wallabag, Shaarli or Instapaper (I use this to automatically pin my bookmarks for collection on my blog)
- Feeds can be configured to download a “full” version of the content (rather than an excerpt)
- Use the Bookmarklet to subscribe to a website directly from any browsers
37.1 Ingredients
- Docker swarm cluster with persistent shared storage
- Traefik configured per design
- DNS entry pointing your Miniflux url (i.e. miniflux.example.com) to your keepalived IP
37.2 Preparation
Setup data locations
Create the location for the bind-mount of the application data, so that it’s persistent:
Setup environment
Create /var/data/config/miniflux/miniflux.env
something like this:
The entire application is configured using environment variables, including the initial username. Once you’ve successfully deployed once, comment out CREATE_ADMIN
and the two successive lines.
Setup Docker Swarm
Create a docker swarm config file in docker-compose syntax (v3), something like this:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
37.3 Serving
Launch Miniflux stack
Launch the Miniflux stack by running docker stack deploy miniflux -c <path -to-docker-compose.yml>
Log into your new instance at https://YOUR-FQDN, using the credentials you setup in the environment flie. After this, change your user/password as you see fit, and comment out the CREATE_ADMIN
line in the env file (if you don’t, then an additional admin will be created the next time you deploy)
37.4 Chef’s Notes
1. Find the bookmarklet under the Settings -> Integration page.
38 Munin
Munin is a networked resource monitoring tool that can help analyze resource trends and “what just happened to kill our performance?” problems. It is designed to be very plug and play. A default installation provides a lot of graphs with almost no work.
Using Munin you can easily monitor the performance of your computers, networks, SANs, applications, weather measurements and whatever comes to mind. It makes it easy to determine “what’s different today” when a performance problem crops up. It makes it easy to see how you’re doing capacity-wise on any resources.
Munin uses the excellent RRDTool (written by Tobi Oetiker) and the framework is written in Perl, while plugins may be written in any language. Munin has a master/node architecture in which the master connects to all the nodes at regular intervals and asks them for data. It then stores the data in RRD files, and (if needed) updates the graphs. One of the main goals has been ease of creating new plugins (graphs).
38.1 Ingredients
- Docker swarm cluster with persistent shared storage
- Traefik configured per design
- DNS entry for the hostname you intend to use, pointed to your keepalived IP
38.2 Preparation
Prepare target nodes
Depending on what you want to monitor, you’ll want to install munin-node. On Ubuntu/Debian, you’ll use apt-get install munin-node
, and on RHEL/CentOS, run yum install munin-node
. Remember to edit /etc/munin/munin-node.conf
, and set your node to allow the server to poll it, by adding cidr_allow x.x.x.x/x
.
On CentOS Atomic, of course, you can’t install munin-node directly, but you can run it as a containerized instance. In this case, you can’t use swarm since you need the container running in privileged mode, so launch a munin-node container on each atomic host using:
Setup data locations
We’ll need several directories to bind-mount into our container, so create them in /var/data/munin:
Prepare environment
Create /var/data/config/munin/munin.env, and populate with the following variables. Use the OAUTH2 variables if you plan to use an oauth2_proxy to protect munin, and set at a minimum the MUNIN_USER
, MUNIN_PASSWORD
, and NODES
values:
Setup Docker Swarm
Create a docker swarm config file in docker-compose syntax (v3), something like this:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
Setup unique static subnets for every stack you deploy. This avoids IP/gateway conflicts which can otherwise occur when you’re creating/removing stacks a lot. See my list here.
38.3 Serving
Launch Munin stack
Launch the Munin stack by running docker stack deploy munin -c <path -to-docker-compose.yml>
Log into your new instance at https://YOUR-FQDN, with user and password password you specified in munin.env above.
38.4 Chef’s Notes
1. If you wanted to expose the Munin UI directly, you could remove the oauth2_proxy from the design, and move the traefik-related labels directly to the munin container. You’d also need to add the traefik_public network to the munin container.
hero: Backup all your stuff. Share it. Privately.
39 NextCloud
Ongoing development of this recipe is sponsored by The Common Observatory. Thanks guys![
NextCloud (a fork of OwnCloud, led by original developer Frank Karlitschek) is a suite of client-server software for creating and using file hosting services. It is functionally similar to Dropbox, although Nextcloud is free and open-source, allowing anyone to install and operate it on a private server.
- https://en.wikipedia.org/wiki/Nextcloud
This recipe is based on the official NextCloud docker image, but includes seprate containers ofor the database (MariaDB), Redis (for transactional locking), Apache Solr (for full-text searching), automated database backup, (you do backup the stuff you care about, right?) and a separate cron container for running NextCloud’s 15-min crons.
39.1 Ingredients
- Docker swarm cluster with persistent shared storage
- Traefik configured per design
- DNS entry pointing your NextCloud url (nextcloud.example.com) to your keepalived IP
39.2 Preparation
Setup data locations
We’ll need several directories for static data to bind-mount into our container, so create them in /var/data/nextcloud (so that they can be backed up)
Now make more directories for runtime data (so that they can be not backed-up):
Prepare environment
Create nextcloud.env, and populate with the following variables
Now create a separate nextcloud-db-backup.env file, to capture the environment variables necessary to perform the backup. (If the same variables are shared with the mariadb container, they cause issues with database access)
Setup Docker Swarm
Create a docker swarm config file in docker-compose syntax (v3), something like this:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
Setup unique static subnets for every stack you deploy. This avoids IP/gateway conflicts which can otherwise occur when you’re creating/removing stacks a lot. See my list here.
39.3 Serving
Launch NextCloud stack
Launch the NextCloud stack by running docker stack deploy nextcloud -c <path -to-docker-compose.yml>
Log into your new instance at https://YOUR-FQDN, with user “admin” and the password you specified in nextcloud.env.
Enable redis
To make NextCloud a little snappier, edit /var/data/nextcloud/config/config.php
(now that it’s been created on the first container launch), and add the following:
Use service discovery
Want to use Calendar/Contacts on your iOS device? Want to avoid dictating long, rambling URL strings to your users, like https://nextcloud.batcave.com/remote.php/dav/principals/users/USERNAME/
?
Huzzah! NextCloud supports service discovery for CalDAV/CardDAV, allowing you to simply tell your device the primary URL of your server (nextcloud.batcave.org, for example), and have the device figure out the correct WebDAV path to use.
We (and anyone else using the NextCloud Docker image) are using an SSL-terminating reverse proxy (Traefik) in front of our NextCloud container. In fact, it’s not possible to setup SSL within the NextCloud container.
When using a reverse proxy, your device requests a URL from your proxy (https://nextcloud.batcave.com/.well-known/caldav), and the reverse proxy then passes that request unencrypted to the internal URL of the NextCloud instance (i.e., http://172.16.12.123/.well-known/caldav)
The Apache webserver on the NextCloud container (knowing it was spoken to via HTTP), responds with a 301 redirect to http://nextcloud.batcave.com/remote.php/dav/. See the problem? You requested an HTTPS (encrypted) url, and in return, you received a redirect to an HTTP (unencrypted) URL. Any sensible client (iOS included) will refuse such schenanigans.
To correct this, we need to tell NextCloud to always redirect the .well-known URLs to an HTTPS location. This can only be done after deploying NextCloud, since it’s only on first launch of the container that the .htaccess file is created in the first place.
To make NextCloud service discovery work with Traefik reverse proxy, edit /var/data/nextcloud/html/.htaccess
, and change this:
To this:
Then restart your container with docker service update nextcloud_nextcloud --force
to restart apache.
Your can test for success by running curl -i https://nextcloud.batcave.org/.well-known/carddav
. You should get a 301 redirect to your equivalent of https://nextcloud.batcave.org/remote.php/dav/, as below:
Note that this .htaccess can be overwritten by NextCloud, and you may have to reapply the change in future. I’ve created an issue requesting a permanent fix.
Ongoing development of this recipe is sponsored by The Common Observatory. Thanks guys![
39.4 Chef’s Notes
- Since many of my other recipes use PostgreSQL, I’d have preferred to use Postgres over MariaDB, but MariaDB seems to be the preferred database type.
- I’m not the first user to stumble across the service discovery bug with reverse proxies.
40 OwnTracks
OwnTracks allows you to keep track of your own location. You can build your private location diary or share it with your family and friends. OwnTracks is open-source and uses open protocols for communication so you can be sure your data stays secure and private.
Using a smartphone app, OwnTracks allows you to collect and analyse your own location data without sharing this data with a cloud provider (i.e. Apple, Google). Potential use cases are:
- Sharing family locations without relying on Apple Find-My-friends
- Performing automated actions in HomeAssistant when you arrive/leave home
40.1 Ingredients
- Docker swarm cluster with persistent shared storage
- Traefik configured per design
- DNS entry for the hostname you intend to use, pointed to your keepalived IP
40.2 Preparation
Setup data locations
We’ll need a directory so store OwnTracks’ data , so create /var/data/owntracks
:
Prepare environment
Create owntracks.env, and populate with the following variables
Setup Docker Swarm
Create a docker swarm config file in docker-compose syntax (v3), something like this:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
Setup unique static subnets for every stack you deploy. This avoids IP/gateway conflicts which can otherwise occur when you’re creating/removing stacks a lot. See my list here.
40.3 Serving
Launch OwnTracks stack
Launch the OwnTracks stack by running docker stack deploy owntracks -c <path -to-docker-compose.yml>
Log into your new instance at https://YOUR-FQDN, with user “root” and the password you specified in gitlab.env.
40.4 Chef’s Notes
- If you wanted to expose the OwnTracks Web UI directly, you could remove the oauth2_proxy from the design, and move the traefik-related labels directly to the wekan container. You’d also need to add the traefik network to the owntracks container.
- I’m using my own image rather than owntracks/recorderd, because of a potentially swarm-breaking bug I found in the official container. If this gets resolved (or if I was mistaken) I’ll update the recipe accordingly.
- By default, you’ll get a fully accessible, unprotected MQTT broker. This may not be suitable for public exposure, so you’ll want to look into securing mosquitto with TLS and ACLs.
41 phpIPAM
phpIPAM is an open-source web IP address management application (IPAM). Its goal is to provide light, modern and useful IP address management. It is php-based application with MySQL database backend, using jQuery libraries, ajax and HTML5/CSS3 features.
phpIPAM fulfils a non-sexy, but important role - It helps you manage your IP address allocation.
41.1 Why should you care about this?
You probably have a home network, with 20-30 IP addresses, for your family devices, your , your smart TV, etc. If you want to (a) monitor them, and (b) audit who does what, you care about what IPs they’re assigned by your DHCP server.
You could simple keep track of all devices with leases in your DHCP server, but what happens if your (hypothetical?) Ubiquity Edge Router X crashes and burns due to lack of disk space, and you loose track of all your leases? Well, you have to start from scratch, is what!
And that HomeAssistant config, which you so carefully compiled, refers to each device by IP/DNS name, so you’d better make sure you recreate it consistently!
Enter phpIPAM. A tool designed to help home keeps as well as large organisations keep track of their IP (and VLAN, VRF, and AS number) allocations.
41.2 Ingredients
- Docker swarm cluster with persistent shared storage
- Traefik configured per design
- DNS entry for the hostname (i.e. “phpipam.your-domain.com”) you intend to use for phpIPAM, pointed to your keepalived IPIP
41.3 Preparation
Setup data locations
We’ll need several directories to bind-mount into our container, so create them in /var/data/phpipam:
Prepare environment
Create phpipam.env, and populate with the following variables
Additionally, create phpipam-backup.env, and populate with the following variables:
Create nginx.conf
I usually protect my stacks using an oauth proxy container in front of the app. This protects me from either accidentally exposing a platform to the world, or having a insecure platform accessed and abused.
In the case of phpIPAM, the oauth_proxy creates an additional complexity, since it passes the “Authorization” HTTP header to the phpIPAM container. phpIPAH then examines the header, determines that the provided username (my email address associated with my oauth provider) doesn’t match a local user account, and denies me access without the opportunity to retry.
The (dirty) solution I’ve come up with is to insert an Nginx instance in the path between the oauth_proxy and the phpIPAM container itself. Nginx can remove the authorization header, so that phpIPAM can prompt me to login with a web-based form.
Create /var/data/phpipam/nginx.conf as follows:
Setup Docker Swarm
Create a docker swarm config file in docker-compose syntax (v3), something like this:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
Setup unique static subnets for every stack you deploy. This avoids IP/gateway conflicts which can otherwise occur when you’re creating/removing stacks a lot. See my list here.
41.4 Serving
Launch phpIPAM stack
Launch the phpIPAM stack by running docker stack deploy phpipam -c <path -to-docker-compose.yml>
Log into your new instance at https://YOUR-FQDN, and follow the on-screen prompts to set your first user/password.
41.5 Chef’s Notes
1. If you wanted to expose the phpIPAM UI directly, you could remove the oauth2_proxy and the nginx services from the design, and move the traefik_public-related labels directly to the phpipam container. You’d also need to add the traefik_public network to the phpipam container.
hero: A recipe to manage your Media
42 Plex
Plex is a client-server media player system and software suite comprising two main components (a media server and client applications)
42.1 Ingredients
- Docker swarm cluster with persistent shared storage
- Traefik configured per design
- A DNS entry for the hostname you intend to use, pointed to your keepalived IP
42.2 Preparation
Setup data locations
We’ll need a directories to bind-mount into our container for Plex to store its library, so create /var/data/plex:
Prepare environment
Create plex.env, and populate with the following variables. Set PUID and GUID to the UID and GID of the user who owns your media files, on the local filesystem
Setup Docker Swarm
Create a docker swarm config file in docker-compose syntax (v3), something like this:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
Setup unique static subnets for every stack you deploy. This avoids IP/gateway conflicts which can otherwise occur when you’re creating/removing stacks a lot. See my list here.
42.3 Serving
Launch Plex stack
Launch the Plex stack by running docker stack deploy plex -c <path -to-docker-compose.yml>
Log into your new instance at https://YOUR-FQDN (You’ll need to setup a plex.tv login for remote access / discovery to work from certain clients)
42.4 Chef’s Notes
- Plex uses port 32400 for remote access, using your plex.tv user/password to authenticate you. The inclusion of the traefik proxy in this recipe is simply to allow you to use the web client (as opposed to a client app) by connecting directly to your instance, as opposed to browsing your media via https://plex.tv/web
- Got an NVIDIA GPU? See this blog post re how to use your GPU to transcode your media!
43 PrivateBin
PrivateBin is a minimalist, open source online pastebin where the server (can) has zero knowledge of pasted data. We all need to paste data / log files somewhere when it doesn’t make sense to paste it inline. With PasteBin, you can own the hosting, access, and eventual deletion of this data.
43.1 Ingredients
- Docker swarm cluster with persistent shared storage
- Traefik configured per design
- DNS entry for the hostname you intend to use, pointed to your keepalived IP
43.2 Preparation
Setup data locations
We’ll need a single location to bind-mount into our container, so create /var/data/privatebin, and make it world-writable (there might be a more secure way to do this!)
Setup Docker Swarm
Create a docker swarm config file in docker-compose syntax (v3), something like this:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
43.3 Serving
Launch PrivateBin stack
Launch the PrivateBin stack by running docker stack deploy privatebin -c <path -to-docker-compose.yml>
Log into your new instance at https://YOUR-FQDN, with user “root” and the password you specified in gitlab.env.
43.4 Chef’s Notes
- The PrivateBin repo explains how to tweak configuration options, or to use a database instead of file storage, if your volume justifies it :)
- The inclusion of PrivateBin was due to the efforts of @gkoerk in our Discord server. Thanks Jerry!!
44 Swarmprom
Swarmprom is a starter kit for Docker Swarm monitoring with Prometheus, Grafana, cAdvisor, Node Exporter, Alert Manager and Unsee. And it’s damn sexy. See for yourself:
So what do all these components do?
- Prometheus is an open-source systems monitoring and alerting toolkit originally built at SoundCloud.
- Grafana is a tool to make data beautiful.
- cAdvisor
cAdvisor (Container Advisor) provides container users an understanding of the resource usage and performance characteristics of their running containers. It is a running daemon that collects, aggregates, processes, and exports information about running containers.
- Node Exporter is a Prometheus exporter for hardware and OS metrics
- Alert Manager Alertmanager handles alerts sent by client applications such as the Prometheus server. It takes care of deduplicating, grouping, and routing them to the correct receiver integrations such as email, Slack, etc.
- Unsee is an alert dashboard for Alert Manager
44.1 How does this magic work?
I’d encourage you to spend some time reading https://github.com/stefanprodan/swarmprom. Stefan has included detailed explanations about which elements perform which functions, as well as how to customize your stack. (This is only a starting point, after all)
44.2 Ingredients
- Docker swarm cluster on 17.09.0 or newer (doesn’t work with CentOS Atomic, unfortunately) with persistent shared storage
- Traefik configured per design
- DNS entry for the hostnames you intend to use, pointed to your keepalived IP
44.3 Preparation
This is basically a rehash of stefanprodan’s instructions to match the way I’ve configured other recipes.
Setup oauth provider
Grafana includes decent login protections, but from what I can see, Prometheus, AlertManager, and Unsee do no authentication. In order to expose these publicly for your own consumption (my assumption for the rest of this recipe), you’ll want to prepare to run oauth_proxy containers in front of each of the 4 web UIs in this recipe.
Setup metrics
Edit (or create, depending on your OS) /etc/docker/daemon.json, and add the following, to enable the experimental export of metrics to Prometheus:
Restart docker with systemctl restart docker
Setup and populate data locations
We’ll need several files to bind-mount into our containers, so create directories for them and get the latest copies:
Prepare Grafana
Grafana will make all the data we collect from our swarm beautiful.
Create /var/data/swarmprom/grafana.env, and populate with the following variables
Setup Docker Swarm
Create a docker swarm config file in docker-compose syntax (v3), based on the original swarmprom docker-compose.yml file
???+ note “This example is 274 lines long. Click here to collapse it for better readability”
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
Setup unique static subnets for every stack you deploy. This avoids IP/gateway conflicts which can otherwise occur when you’re creating/removing stacks a lot. See my list here.
44.4 Serving
Launch Swarmprom stack
Launch the Swarm stack by running docker stack deploy swarmprom -c <path -to-docker-compose.yml>
Log into your new grafana instance, check out your beautiful graphs. Move onto drooling over Prometheus, AlertManager, and Unsee.
44.5 Chef’s Notes
1. Pay close attention to the grafana.env
config. If you encounter errors about basic auth failed
, or failed CSS, it’s likely due to misconfiguration of one of the grafana environment variables.
III Recipies (Docker)
Now follows individual recipes.
45 Bitwarden
Heard about the latest password breach (since lunch)? HaveYouBeenPowned yet (today)? Passwords are broken, and as the amount of sites for which you need to store credentials grows exponetially, so does the risk of using a common password.
“Duh, use a password manager”, you say. Sure, but be aware that even password managers have security flaws.
OK, look smartass.. no software is perfect, and there will always be a risk of your credentials being exposed in ways you didn’t intend. You can at least minimize the impact of such exposure by using a password manager to store unique credentials per-site. While 1Password is king of the commercial password manager, BitWarden is king of the open-source, self-hosted password manager.
Enter Bitwarden..
Bitwarden is a free and open source password management solution for individuals, teams, and business organizations. While Bitwarden does offer a paid / hosted version, the free version comes with the following (better than any other free password manager!):
- Access & install all Bitwarden apps
- Sync all of your devices, no limits!
- Store unlimited items in your vault
- Logins, secure notes, credit cards, & identities
- Two-step authentication (2FA)
- Secure password generator
- Self-host on your own server (optional)
45.1 Ingredients
Existing:1. [X] Docker swarm cluster with persistent shared storage
2. [X] Traefik configured per design
3. [X] DNS entry for the hostname you intend to use, pointed to your keepalived IP
45.2 Preparation
Setup data locations
We’ll need to create a directory to bind-mount into our container, so create /var/data/bitwarden
:
### Setup environment
Create /var/data/config/bitwarden/bitwarden.env
, and leave it empty for now.
Setup Docker Swarm
Create a docker swarm config file in docker-compose syntax (v3), something like this:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
Note the clever use of two Traefik frontends to expose the notifications hub on port 3012. Thanks @gkoerk!
45.3 Serving
Launch Bitwarden stack
Launch the Bitwarden stack by running docker stack deploy bitwarden -c <path -to-docker-compose.yml>
Browse to your new instance at https://YOUR-FQDN, and create a new user account and master password (Just click the Create Account button without filling in your email address or master password)
Get the apps / extensions
Once you’ve created your account, jump over to https://bitwarden.com/#download and download the apps for your mobile and browser, and start adding your logins!
45.4 Chef’s Notes
- You’ll notice we’re not using the official container images (all 6 of them required!), but rather a more lightweight version ideal for self-hosting. All of the elements are contained within a single container, and SQLite is used for the database backend.
- As mentioned above, readers should refer to the dani-garcia/bitwarden_rs wiki for details on customizing the behaviour of Bitwarden.
- The inclusion of Bitwarden was due to the efforts of @gkoerk in our Discord server- Thanks Gerry!
46 BookStack
BookStack is a simple, self-hosted, easy-to-use platform for organising and storing information.
A friendly middle ground between heavyweights like MediaWiki or Confluence and Gollum, BookStack relies on a database backend (so searching and versioning is easy), but limits itself to a pre-defined, 3-tier structure (book, chapter, page). The result is a lightweight, approachable personal documentation stack, which includes search and Markdown editing.
I like to protect my public-facing web UIs with an oauth_proxy, ensuring that if an application bug (or a user misconfiguration) exposes the app to unplanned public scrutiny, I have a second layer of defense.
46.1 Ingredients
- Docker swarm cluster with persistent shared storage
- Traefik configured per design
- DNS entry for the hostname you intend to use, pointed to your keepalived IP
46.2 Preparation
Setup data locations
We’ll need several directories to bind-mount into our container, so create them in /var/data/bookstack:
Prepare environment
Create bookstack.env, and populate with the following variables. Set the oauth_proxy variables provided by your OAuth provider (if applicable.)
Setup Docker Swarm
Create a docker swarm config file in docker-compose syntax (v3), something like this:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
Setup unique static subnets for every stack you deploy. This avoids IP/gateway conflicts which can otherwise occur when you’re creating/removing stacks a lot. See my list here.
46.3 Serving
Launch Bookstack stack
Launch the BookStack stack by running docker stack deploy bookstack -c <path -to-docker-compose.yml>
Log into your new instance at https://YOUR-FQDN, authenticate with oauth_proxy, and then login with username ‘admin@admin.com’ and password ‘password’.
46.4 Chef’s Notes
1. If you wanted to expose the BookStack UI directly, you could remove the oauth2_proxy from the design, and move the traefik_public-related labels directly to the bookstack container. You’d also need to add the traefik_public network to the bookstack container.
hero: Manage your ebook collection. Like a BOSS.
47 Calibre-Web
The AutoPirate recipe includes Lazy Librarian, a tool for tracking, finding, and downloading eBooks. However, after the eBooks are downloaded, Lazy Librarian is not much use for organising, tracking, and actually reading them.
Calibre-Web could be described as “Plex (or Emby) for eBooks” - it’s a web-based interface to manage your eBook library, screenshot below:
Of course, you probably already manage your eBooks using the excellent Calibre, but this is primarily a (powerful) desktop application. Calibre-Web is an alternative way to manage / view your existing Calibre database, meaning you can continue to use Calibre on your desktop if you wish.
As a long-time Kindle user, Calibre-Web brings (among others) the following features which appeal to me:
- Filter and search by titles, authors, tags, series and language
- Create custom book collection (shelves)
Support for editing eBook metadata and deleting eBooks from Calibre library
- Support for converting eBooks from EPUB to Kindle format (mobi/azw)
- Send eBooks to Kindle devices with the click of a button
- Support for reading eBooks directly in the browser (.txt, .epub, .pdf, .cbr, .cbt, .cbz)
- Upload new books in PDF, epub, fb2 format
47.1 Ingredients
- Docker swarm cluster with persistent shared storage
- Traefik configured per design
- DNS entry for the hostname you intend to use, pointed to your keepalived IP
47.2 Preparation
Setup data locations
We’ll need a directory to store some config data for Calibre-Web, container, so create /var/data/calibre-web, and ensure the directory is owned by the same use which owns your Calibre data (below)
Ensure that your Calibre library is accessible to the swarm (i.e., exists on shared storage), and that the same user who owns the config directory above, also owns the actual calibre library data (including the ebooks managed by Calibre).
Prepare environment
We’ll use an oauth-proxy to protect the UI from public access, so create calibre-web.env, and populate with the following variables:
Follow the instructions to setup your oauth provider. You need to setup a unique key/secret for each instance of the proxy you want to run, since in each case the callback URL will differ.
Setup Docker Swarm
Create a docker swarm config file in docker-compose syntax (v3), something like this:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
Setup unique static subnets for every stack you deploy. This avoids IP/gateway conflicts which can otherwise occur when you’re creating/removing stacks a lot. See my list here.
47.3 Serving
Launch Calibre-Web
Launch the Calibre-Web stack by running docker stack deploy calibre-web -c <path -to-docker-compose.yml>
Log into your new instance at https://YOUR-FQDN. You’ll be directed to the initial GUI configuraition. Set the first field (Location of Calibre database) to “/books/”, and when complete, login using defaults username of “admin” with password “admin123”.
47.4 Chef’s Notes
- Yes, Calibre does provide a server component. But it’s not as fully-featured as Calibre-Web (i.e., you can’t use it to send ebooks directly to your Kindle)
- A future enhancement might be integrating this recipe with the filestore for NextCloud, so that the desktop database (Calibre) can be kept synced with Calibre-Web.
48 Collabora Online
Development of this recipe is sponsored by The Common Observatory. Thanks guys![
Collabora Online Development Edition (or “CODE”), is the lightweight, or “home” edition of the commercially-supported Collabora Online platform. It
It’s basically the LibreOffice interface in a web-browser. CODE is not a standalone app, it’s a backend intended to be accessed via “WOPI” from an existing interface (in our case, NextCloud)
48.1 Ingredients
- Docker swarm cluster with persistent shared storage
- Traefik configured per design
- DNS entry for the hostname (i.e. “collabora.your-domain.com”) you intend to use for LDAP Account Manager, pointed to your keepalived IP
- NextCloud installed and operational
- Docker-compose installed on your node(s) - this is a special case which needs to run outside of Docker Swarm
48.2 Preparation
Explanation for complexity
Due to the clever magic that Collabora does to present a “headless” LibreOffice UI to the browser, the CODE docker container requires system capabilities which cannot be granted under Docker Swarm (specifically, MKNOD).
So we have to run Collabora itself in the next best thing to Docker swarm - a docker-compose stack. Using docker-compose will at least provide us with consistent and version-able configuration files.
This presents another problem though - Docker Swarm with Traefik is superb at making all our stacks “just work” with ingress routing and LetsEncyrpt certificates. We don’t want to have to do this manually (like a cave-man), so we engage in some trickery to allow us to still use our swarmed Traefik to terminate SSL.
We run a single swarmed Nginx instance, which forwards all requests to an upstream, with the target IP of the docker0 interface, on port 9980 (the port exposed by the CODE container)
We attach the necessary labels to the Nginx container to instruct Trafeik to setup a front/backend for collabora.<ourdomain>. Now incoming requests to https://collabora.<ourdomain> will hit Traefik, be forwarded to nginx (wherever in the swarm it’s running), and then to port 9980 on the same node that nginx is running on.
What if we’re running multiple nodes in our swarm, and nginx ends up on a different node to the one running Collabora via docker-compose? Well, either constrain nginx to the same node as Collabora (example below), or just launch an instance of Collabora on every node then. It’s just a rendering / GUI engine after all, it doesn’t hold any persistent data.
Here’s a (highly technical) diagram to illustrate:
Setup data locations
We’ll need a directory for holding config to bind-mount into our containers, so create /var/data/collabora
, and /var/data/config/collabora
for holding the docker/swarm config
Prepare environment
Create /var/data/config/collabora/collabora.env, and populate with the following variables, customized for your installation.
Note the following:1. Variables are in lower-case, unlike our standard convention. This is to align with the CODE container
2. Set domain to your NextCloud domain, and escape all the periods as per the example
3. Set your server_name to collabora.<yourdomain>. Escaping periods is unnecessary
4. Your password cannot include triangular brackets - the entrypoint script will insert this password into an XML document, and triangular brackets will make bad(tm) things happen
Create docker-compose.yml
Create /var/data/config/collabora/docker-compose.yml
as follows:
Create nginx.conf
Create /var/data/config/collabora/nginx.conf
as follows, changing the server_name
value to match the environment variable you established above:
Create loolwsd.xml
Until we understand how to pass trusted network parameters to the entrypoint script using environment variables, we have to maintain a manually edited version of loolwsd.xml
, and bind-mount it into our collabora container.
The way we do this is we mount/var/data/collabora/loolwsd.xml
as /etc/loolwsd/loolwsd.xml-new
, then allow the container to create its default /etc/loolwsd/loolwsd.xml
, copy this default over our /var/data/collabora/loolwsd.xml
as /etc/loolwsd/loolwsd.xml-new
, and then update the container to use our /var/data/collabora/loolwsd.xml
as /etc/loolwsd/loolwsd.xml
instead (confused yet?)
Create an empty /var/data/collabora/loolwsd.xml
by running touch /var/data/collabora/loolwsd.xml
. We’ll populate this in the next section…
Setup Docker Swarm
Create /var/data/config/collabora/collabora.yml
as follows, changing the traefik frontend_rule as necessary:
git pull
and a docker stack deploy
48.3 Serving
Generate loolwsd.xml
Well. This is awkward. There’s no documented way to make Collabora work with Docker Swarm, so we’re doing a bit of a hack here, until I understand how to pass these arguments via environment variables.
Launching Collabora is (for now) a 2-step process. First.. we launch collabora itself, by running:
Output looks something like this:
Now exec into the container (from another shell session), by running exec <container name> -it /bin/bash
. Make a copy of /etc/loolwsd/loolwsd, by running cp /etc/loolwsd/loolwsd.xml /etc/loolwsd/loolwsd.xml-new
, and then exit the container with exit
.
Delete the collabora container by hitting CTRL-C in the docker-compose shell, running docker-compose rm
, and then altering this line in docker-compose.yml:
To this:
Edit /var/data/collabora/loolwsd.xml, find the storage.filesystem.wopi section, and add lines like this to the existing allow rules (to allow IPv6-enabled hosts to still connect with their IPv4 addreses):
Find the net.post_allow section, and add a line like this:
Find these 2 lines:
And change to:
Now re-launch collabora (with the correct with loolwsd.xml) under docker-compose, by running:
Once collabora is up, we launch the swarm stack, by running:
Visit https://collabora.<yourdomain>/l/loleaflet/dist/admin/admin.html and confirm you can login with the user/password you specified in collabora.env
Integrate into NextCloud
In NextCloud, Install the Collabora Online app (https://apps.nextcloud.com/apps/richdocuments), and then under Settings -> Collabora Online, set your Collabora Online Server to https://collabora.<your domain>
Now browse your NextCloud files. Click the plus (+) sign to create a new document, and create either a new document, spreadsheet, or presentation. Name your document and then click on it. If Collabora is setup correctly, you’ll shortly enter into the rich editing interface provided by Collabora :)
Development of this recipe is sponsored by The Common Observatory. Thanks guys![
48.4 Chef’s Notes
1. Yes, this recipe is complicated. And you probably only care if you feel strongly about using Open Source rich document editing in the browser, vs using something like Google Docs. It works impressively well however, once it works. I hope to make this recipe simpler once the CODE developers have documented how to pass optional parameters as environment variables.
hero: Ghost - A recipe for beautiful online publication.
49 Ghost
Ghost is “a fully open source, hackable platform for building and running a modern online publication.”
49.1 Ingredients
Existing:1. [X] Docker swarm cluster with persistent shared storage
2. [X] Traefik configured per design
3. [X] DNS entry for the hostname you intend to use, pointed to your keepalived IP
49.2 Preparation
Setup data locations
Create the location for the bind-mount of the application data, so that it’s persistent:
Setup Docker Swarm
Create a docker swarm config file in docker-compose syntax (v3), something like this:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
49.3 Serving
Launch Ghost stack
Launch the Ghost stack by running docker stack deploy ghost -c <path -to-docker-compose.yml>
Create your first administrative account at https://YOUR-FQDN/admin/
49.4 Chef’s Notes
- If I wasn’t committed to a static-site-generated blog, Ghost is the platform I’d use for my blog.
- A default using the SQlite database takes 548k of space:
hero: Gitlab - A recipe for a self-hosted GitHub alternative
50 GitLab
GitLab is a self-hosted alternative to GitHub. The most common use case is (a set of) developers with the desire for the rich feature-set of GitHub, but with unlimited private repositories.
Docker does maintain an official “Omnibus” container, but for this recipe I prefer the “dockerized gitlab” project, since it allows distribution of the various Gitlab components across multiple swarm nodes.
50.1 Ingredients
Existing:1. [X] Docker swarm cluster with persistent shared storage
2. [X] Traefik configured per design
3. [X] DNS entry for the hostname you intend to use, pointed to your keepalived IP
50.2 Preparation
Setup data locations
We’ll need several directories to bind-mount into our container, so create them in /var/data/gitlab:
Prepare environment
You’ll need to know the following:
1. Choose a password for postgresql, you’ll need it for DB_PASS in the compose file (below)
2. Generate 3 passwords using pwgen -Bsv1 64
. You’ll use these for the XXX_KEY_BASE environment variables below
- Create gitlab.env, and populate with at least the following variables (the full set is available at https://github.com/sameersbn/docker-gitlab#available-configuration-parameters):
Setup Docker Swarm
Create a docker swarm config file in docker-compose syntax (v3), something like this:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
Setup unique static subnets for every stack you deploy. This avoids IP/gateway conflicts which can otherwise occur when you’re creating/removing stacks a lot. See my list here.
50.3 Serving
Launch gitlab
Launch the mail server stack by running docker stack deploy gitlab -c <path -to-docker-compose.yml>
Log into your new instance at https://[your FQDN], with user “root” and the password you specified in gitlab.env.
50.4 Chef’s Notes
A few comments on decisions taken in this design:
- I use the sameersbn/gitlab:latest image, rather than a specific version. This lets me execute updates simply by redeploying the stack (and why wouldn’t I want the latest version?)
51 Gitlab Runner
Some features of GitLab require a “runner” (in the sense of a “gopher” or a “minion”). A runner “registers” itself with a GitLab instance, and is given tasks to run. Tasks include running Continuous Integration (CI) builds, and building container images.
While a runner isn’t strictly required to use GitLab, if you want to do CI, you’ll need at least one. There are many ways to deploy a runner - this recipe focuses on the docker container model.
51.1 Ingredients
Existing:1. [X] Docker swarm cluster with persistent shared storage
2. [X] Traefik configured per design
3. [X] DNS entry for the hostname you intend to use, pointed to your keepalived IP
4. [X] GitLab installation (see previous recipe)
51.2 Preparation
Setup data locations
We’ll need several directories to bind-mount into our runner containers, so create them in /var/data/gitlab
:
Setup Docker Swarm
Create a docker swarm config file in docker-compose syntax (v3), something like this:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
Configure runners
From your GitLab UI, you can retrieve a “token” necessary to register a new runner. To register the runner, you can either create config.toml in each runner’s bind-mounted folder (example below), or just docker exec
into each runner container and execute gitlab-runner register
to interactively generate config.toml.
Sample runner config.toml:
51.3 Serving
Launch runners
Launch the mail server stack by running docker stack deploy gitlab-runner -c <path -to-docker-compose.yml>
Log into your new instance at https://YOUR-FQDN, with user “root” and the password you specified in gitlab.env.
51.4 Chef’s Notes
- You’ll note that I setup 2 runners. One is locked to a single project (this cookbook build), and the other is a shared runner. I wanted to ensure that one runner was always available to run CI for this project, even if I’d tied up another runner on something heavy-duty, like a container build. Customize this to your use case.
- Originally I deployed runners in the same stack as GitLab, but I found that they would frequently fail to start properly when I launched the stack. I think that this was because the runners started so quickly (and GitLab starts sooo slowly!), that they always started up reporting that the GitLab instance was invalid or unavailable. I had issues with CI builds stuck permanently in a “pending” state, which were only resolved by restarting the runner. Having the runners deployed in a separate stack to GitLab avoids this problem.
hero: Gollum - A recipe for your own git-based wiki
52 Gollum
Gollum is a simple wiki system built on top of Git. A Gollum Wiki is simply a git repository (either bare or regular) of a specific nature:
- A Gollum repository’s contents are human-editable, unless the repository is bare.
- Pages are unique text files which may be organized into directories any way you choose.
- Other content can also be included, for example images, PDFs and headers/footers for your pages.
Gollum pages:
- May be written in a variety of markups.
- Can be edited with your favourite system editor or IDE (changes will be visible after committing) or with the built-in web interface.
- Can be displayed in all versions (commits).
As you’ll note in the (real world) screenshot above, my requirements for a personal wiki are:
- Portable across my devices
- Supports images
- Full-text search
- Supports inter-note links
- Revision control
Gollum meets all these requirements, and as an added bonus, is extremely fast and lightweight.
Since Gollum itself offers no user authentication, this design secures gollum behind an oauth2 proxy, so that in order to gain access to the Gollum UI at all, oauth2 authentication (to GitHub, GitLab, Google, etc) must have already occurred.52.1 Ingredients
Existing:1. [X] Docker swarm cluster with persistent shared storage
2. [X] Traefik configured per design
3. [X] DNS entry for the hostname you intend to use, pointed to your keepalived IP
52.2 Preparation
Setup data locations
We’ll need an empty git repository in /var/data/gollum for our data:
Prepare environment
- Choose an oauth provider, and obtain a client ID and secret
- Create gollum.env, and populate with the following variables (you can make the cookie secret whatever you like)
Setup Docker Swarm
Create a docker swarm config file in docker-compose syntax (v3), something like this:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
Setup unique static subnets for every stack you deploy. This avoids IP/gateway conflicts which can otherwise occur when you’re creating/removing stacks a lot. See my list here.
52.3 Serving
Launch Gollum stack
Launch the Gollum stack by running docker stack deploy gollum -c <path-to-docker-compose.yml>
Authenticate against your OAuth provider, and then start editing your wiki!
52.4 Chef’s Notes
1. In the current implementation, Gollum is a “single user” tool only. The contents of the wiki are saved as markdown files under /var/data/gollum, and all the git commits are currently “Anonymous”
53 InstaPy
InstaPy is an Instagram bot, developed by Tim Grossman. Tim describes his motivation and experiences developing the bot here.
What’s an Instagram bot? Basically, you feed the bot your Instagram user/password, and it executes follows/unfollows/likes/comments on your behalf based on rules you set. (I set my bot to like one photo tagged with “#penguin” per-run)
Great power, right? A client (yes, you can hire me!) asked me to integrate InstaPy into their swarm, and this recipe is the result.
53.1 Ingredients
Existing:1. [X] Docker swarm cluster with persistent shared storage
2. [X] Traefik configured per design
3. [X] DNS entry for the hostname you intend to use, pointed to your keepalived IP
53.2 Preparation
Setup data locations
We need a data location to store InstaPy’s config, as well as its log files. Create /var/data/instapy per below
Setup Docker Swarm
Create a docker swarm config file in docker-compose syntax (v3), something like this:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
Setup unique static subnets for every stack you deploy. This avoids IP/gateway conflicts which can otherwise occur when you’re creating/removing stacks a lot. See my list here.
Command your bot
Create a variation of https://github.com/timgrossmann/InstaPy/blob/master/docker_quickstart.py at /var/data/instapy/instapy.py (the file we bind-mounted in the swarm config above)
Change at least the following:
Here’s an example of my config, set to like a single penguin-pic per run:
53.3 Serving
Destroy all humans
Launch the bot by running docker stack deploy instapy -c <path -to-docker-compose.yml>
While you’re waiting for Docker to pull down the images, educate yourself on the risk of a robotic uprising:
<iframe width=”560” height=”315” src=”https://www.youtube.com/embed/B1BdQcJ2ZYY” frameborder=”0” allow=”autoplay; encrypted-media” allowfullscreen></iframe>
After swarm deploys, you won’t see much, but you can monitor what InstaPy is doing, by running docker service logs instapy_web
.
You can also watch the bot at work by VNCing to your docker swarm, password “secret”. You’ll see Selenium browser window cycling away, interacting with all your real/fake friends on Instagram :)
53.4 Chef’s Notes
1. Amazingly, my bot has ended up tagging more non-penguins than actual penguins. I don’t understand how Instagrammers come up with their hashtags!
54 KeyCloak
KeyCloak is “an open source identity and access management solution”. Using a local database, or a variety of backends (think OpenLDAP), you can provide Single Sign-On (SSO) using OpenID, OAuth 2.0, and SAML. KeyCloak’s OpenID provider can be used in combination with Traefik Forward Auth, to protect vulnerable services with an extra layer of authentication.
Initial development of this recipe was sponsored by The Common Observatory. Thanks guys![
54.1 Ingredients
Existing:* [X] Docker swarm cluster with persistent shared storage
* [X] Traefik configured per design
* [X] DNS entry for the hostname (i.e. “keycloak.your-domain.com”) you intend to use, pointed to your keepalived IP
54.2 Preparation
Setup data locations
We’ll need several directories to bind-mount into our container for both runtime and backup data, so create them as follows
Prepare environment
Create /var/data/keycloak/keycloak.env
, and populate with the following variables, customized for your own domain structure.
Create /var/data/keycloak/keycloak-backup.env
, and populate with the following, so that your database can be backed up to the filesystem, daily:
Setup Docker Swarm
Create a docker swarm config file in docker-compose syntax (v3), something like this:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
Setup unique static subnets for every stack you deploy. This avoids IP/gateway conflicts which can otherwise occur when you’re creating/removing stacks a lot. See my list here.
54.3 Serving
Launch KeyCloak stack
Launch the KeyCloak stack by running docker stack deploy keycloak -c <path -to-docker-compose.yml>
Log into your new instance at https://YOUR-FQDN, and login with the user/password you defined in keycloak.env
.
[
54.4 Chef’s Notes
55 Create KeyCloak Users
This is not a complete recipe - it’s an optional component of the Keycloak recipe, but has been split into its own page to reduce complexity.Unless you plan to authenticate against an outside provider (OpenLDAP, below, for example), you’ll want to create some local users..
55.1 Ingredients
Existing:* [X] KeyCloak recipe deployed successfully
Create User
Within the “Master” realm (no need for more realms yet), navigate to Manage -> Users, and then click Add User at the top right:
Populate your new user’s username (it’s the only mandatory field)
Set User Credentials
Once your user is created, to set their password, click on the “Credentials” tab, and procede to reset it. Set the password to non-temporary, unless you like extra work!
55.2 Summary
We’ve setup users in KeyCloak, which we can now use to authenticate to KeyCloak, when it’s used as an OIDC Provider, potentially to secure vulnerable services using Traefik Forward Auth.
Created:* [X] Username / password to authenticate against KeyCloak
56 Authenticate KeyCloak against OpenLDAP
This is not a complete recipe - it’s an optional component of the Keycloak recipe, but has been split into its own page to reduce complexity.KeyCloak gets really sexy when you integrate it into your OpenLDAP stack (also, it’s great not to have to play with ugly LDAP tree UIs). Note that OpenLDAP integration is not necessary if you want to use KeyCloak with Traefik Forward Auth - all you need for that is local users, and an OIDC client.
56.1 Ingredients
Existing:* [X] KeyCloak recipe deployed successfully
New:
* [ ] An OpenLDAP server (assuming you want to authenticate against it)
56.2 Preparation
You’ll need to have completed the OpenLDAP recipe
You start in the “Master” realm - but mouseover the realm name, to a dropdown box allowing you add an new realm:
Create Realm
Enter a name for your new realm, and click “Create”:
Setup User Federation
Once in the desired realm, click on User Federation, and click Add Provider. On the next page (“Required Settings”), set the following:
- Edit Mode : Writeable
- Vendor : Other
- Connection URL : ldap://openldap
- Users DN : ou=People,<your base DN>
- Authentication Type : simple
- Bind DN : cn=admin,<your base DN>
- Bind Credential : <your chosen admin password>
Save your changes, and then navigate back to “User Federation” > Your LDAP name > Mappers:
For each of the following mappers, click the name, and set the “Read Only” flag to “Off” (this enables 2-way sync between KeyCloak and OpenLDAP)
- last name
- username
- first name
56.3 Summary
We’ve setup a new realm in KeyCloak, and configured read-write federation to an OpenLDAP backend. We can now manage our LDAP users using either KeyCloak or LDAP directly, and we can protect vulnerable services using Traefik Forward Auth.
Created:* [X] KeyCloak realm in read-write federation with OpenLDAP directory
56.4 Chef’s Notes
57 Add OIDC Provider to KeyCloak
This is not a complete recipe - it’s an optional component of the Keycloak recipe, but has been split into its own page to reduce complexity.Having an authentication provider is not much use until you start authenticating things against it! In order to authenticate against KeyCloak using OpenID Connect (OIDC), which is required for Traefik Forward Auth, we’ll setup a client in KeyCloak…
57.1 Ingredients
Existing:* [X] KeyCloak recipe deployed successfully
New:
* [ ] The URI(s) to protect with the OIDC provider. Refer to the Traefik Forward Auth recipe for more information
57.2 Preparation
Create Client
Within the “Master” realm (no need for more realms yet), navigate to Clients, and then click Create at the top right:
Enter a name for your client (remember, we’re authenticating applications now, not users, so use an application-specific name):
Configure Client
Once your client is created, set at least the following, and click Save
- Access Type : Confidential
- Valid Redirect URIs : <The URIs you want to protect>
Retrieve Client Secret
Now that you’ve changed the access type, and clicked Save, an additional Credentials tab appears at the top of the window. Click on the tab, and capture the KeyCloak-generated secret. This secret, plus your client name, is required to authenticate against KeyCloak via OIDC.
57.3 Summary
We’ve setup an OIDC client in KeyCloak, which we can now use to protect vulnerable services using Traefik Forward Auth. The OIDC URL provided by KeyCloak in the master realm, is https://<your-keycloak-url>/realms/master/.well-known/openid-configuration
Created:* [X] Client ID and Client Secret used to authenticate against KeyCloak with OpenID Connect
57.4 Chef’s Notes
58 OpenLDAP
Development of this recipe is sponsored by The Common Observatory. Thanks guys![
LDAP is probably the most ubiquitous authentication backend, before the current era of “stupid social sign-ons”. Many of the recipes featured in the cookbook (NextCloud, Kanboard, Gitlab, etc) offer LDAP integration.
58.1 Big deal, who cares?
If you’re the only user of your tools, it probably doesn’t bother you too much to setup new user accounts for every tool. As soon as you start sharing tools with collaborators (think 10 staff using NextCloud), you suddenly feel the pain of managing a growing collection of local user accounts per-service.
Enter OpenLDAP - the most crusty, PITA, fiddly platform to setup (yes, I’m a little bitter, dynamic configuration backend!), but hugely useful for one job - a Lightweight Protocol for managing a Directory used for Access (see what I did there?)
The nice thing about OpenLDAP is, like MySQL, once you’ve setup the server, you probably never have to interact directly with it. There are many tools which will let you interact with your LDAP database via a(n ugly) UI.
This recipe combines the raw power of OpenLDAP with the flexibility and featureset of LDAP Account Manager.
58.2 What’s the takeaway?
What you’ll end up with is a directory structure which will allow integration with popular tools (NextCloud, Kanboard, Gitlab, etc), as well as with KeyCloak (an upcoming recipe), for true SSO.
58.3 Ingredients
- Docker swarm cluster with persistent shared storage
- Traefik configured per design
- DNS entry for the hostname (i.e. “lam.your-domain.com”) you intend to use for LDAP Account Manager, pointed to your keepalived IP
58.4 Preparation
Setup data locations
We’ll need several directories to bind-mount into our container, so create them in /var/data/openldap:
For rationale, see my data layout explanationPrepare environment
Create /var/data/openldap/openldap.env, and populate with the following variables, customized for your own domain structure. Take care with LDAP_DOMAIN, this is core to your directory structure, and can’t easily be changed later.
I use an OAuth proxy to protect access to the web UI, when the sensitivity of the protected data (i.e. my authentication store) warrants it, or if I don’t necessarily trust the security of the webUI.Create authenticated-emails.txt
, and populate with the email addresses (matched to GitHub user accounts, in my case) to which you want grant access, using OAuth2.
Create config.cfg
The Dockerized version of LDAP Account Manager is a little fiddly. In order to maintain a config file which persists across container restarts, we need to present the container with a copy of /var/www/html/config/lam.conf, tweaked for our own requirements.
Create /var/data/openldap/lam/config/config.cfg
as follows:
???+ note “Much scroll, very text. Click here to collapse it for better readability”
Create <profile>.cfg
While config.cfg (above) defines application-level configuration, <profile>.cfg is used to configure “domain-specific” configuration. You probably only need a single profile, but LAM could theoretically be used to administer several totally unrelated LDAP servers, ergo the concept of “profiles”.
Create yours profile (you chose a default profile in config.cfg above, remember?) by creating /var/data/openldap/lam/config/<profile>.conf
, as follows:
???+ note “Much scroll, very text. Click here to collapse it for better readability”
Setup Docker Swarm
Create a docker swarm config file in docker-compose syntax (v3), something like this, at (/var/data/config/openldap/openldap.yml
)
git pull
and a docker stack deploy
Normally, we set unique static subnets for every stack you deploy, and put the non-public facing components (like databases) in an dedicated <stack>_internal network. This avoids IP/gateway conflicts which can otherwise occur when you’re creating/removing stacks a lot. See my list here.
However, you’re likely to want to use OpenLdap with KeyCloak, whose JBOSS startup script assumes a single interface, and will crash in a ball of if you try to assign multiple interfaces to the container.
Since we’re going to want KeyCloak to be able to talk to OpenLDAP, we have no choice but to leave the OpenLDAP container on the “traefik_public” network. We can, however, create another overlay network (auth_internal, see below), add it to the openldap container, and use it to provide OpenLDAP access to our other stacks.
Create another stack config file (/var/data/config/openldap/auth.yml
) containing just the auth_internal network, and a dummy container:
58.5 Serving
Launch OpenLDAP stack
Create the auth_internal overlay network, by running docker stack deploy auth -c /var/data/config/openldap/auth.yml
, then launch the OpenLDAP stack by running docker stack deploy openldap -c /var/data/config/openldap/openldap.yml
Log into your new LAM instance at https://YOUR-FQDN.
On first login, you’ll be prompted to create the “ou=People” and “ou=Group” elements. Proceed to create these.
You’ve now setup your OpenLDAP directory structure, and your administration interface, and hopefully won’t have to interact with the “special” LDAP Account Manager interface much again!
Create your users using the “New User” button.
Development of this recipe is sponsored by The Common Observatory. Thanks guys![
58.6 Chef’s Notes
- The KeyCloak recipe illustrates how to integrate KeyCloak with your LDAP directory, giving you a cleaner interface to manage users, and a raft of SSO / OAuth features.
hero: Docker-mailserver - A recipe for a self-contained mailserver and friends
59 Mail Server
Many of the recipes that follow require email access of some kind. It’s normally possible to use a hosted service such as SendGrid, or just a gmail account. If (like me) you’d like to self-host email for your stacks, then the following recipe provides a full-stack mail server running on the docker HA swarm.
Of value to me in choosing docker-mailserver were:
- Automatically renews LetsEncrypt certificates
- Creation of email accounts across multiple domains (i.e., the same container gives me mailbox wekan@wekan.example.com, and gitlab@gitlab.example.com)
- The entire configuration is based on flat files, so there’s no database or persistence to worry about
docker-mailserver doesn’t include a webmail client, and one is not strictly needed. Rainloop can be added either as another service within the stack, or as a standalone service. Rainloop will be covered in a future recipe.
59.1 Ingredients
- Docker swarm cluster with persistent shared storage
- Traefik configured per design
- LetsEncrypt authorized email address for domain
- Access to manage DNS records for domains
59.2 Preparation
Setup data locations
We’ll need several directories to bind-mount into our container, so create them in /var/data/docker-mailserver:
Get LetsEncrypt certificate
Decide on the FQDN to assign to your mailserver. You can service multiple domains from a single mailserver - i.e., bob@dev.example.com and daphne@prod.example.com can both be served by mail.example.com.
The docker-mailserver container can renew our LetsEncrypt certs for us, but it can’t generate them. To do this, we need to run certbot (from a container) to request the initial certs and create the appropriate directory structure.
In the example below, since I’m already using Traefik to manage the LE certs for my web platforms, I opted to use the DNS challenge to prove my ownership of the domain. The certbot client will prompt you to add a DNS record for domain verification.
Get setup.sh
docker-mailserver comes with a handy bash script for managing the stack (which is just really a wrapper around the container.) It’ll make our setup easier, so download it into the root of your configuration/data directory, and make it executable:
### Create email accounts
For every email address required, run ./setup.sh email add <email> <password>
to create the account. The command returns no output.
You can run ./setup.sh email list
to confirm all of your addresses have been created.
Create DKIM DNS entries
Run ./setup.sh config dkim
to create the necessary DKIM entries. The command returns no output.
Examine the keys created by opendkim to identify the DNS TXT records required:
You’ll end up with something like this:
Create the necessary DNS TXT entries for your domain(s). Note that although opendkim splits the record across two lines, the actual record should be concatenated on creation. I.e., the DNS TXT record above should read:
Setup Docker Swarm
Create a docker swarm config file in docker-compose syntax (v3.2 - because we need to expose mail ports in “host mode”), something like this:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
Setup unique static subnets for every stack you deploy. This avoids IP/gateway conflicts which can otherwise occur when you’re creating/removing stacks a lot.
A sample docker-mailserver.env file looks like this:
59.3 Serving
Launch mailserver
Launch the mail server stack by running docker stack deploy docker-mailserver -c <path-to-docker-mailserver.yml>
59.4 Chef’s Notes
- One of the elements of this design which I didn’t appreciate at first is that since the config is entirely file-based, setup.sh can be run on any container host, provided it has the shared data mounted. This means that even though docker-mailserver was not designed with docker swarm in mind, it works perfectl with swarm. I.e., from any node, regardless of where the container is actually running, you’re able to add/delete email addresses, view logs, etc.
- If you’re using sieve with Rainloop, take note of the workaround identified by ggilley
60 Minio
Minio is a high performance distributed object storage server, designed for
large-scale private cloud infrastructure.
However, at its simplest, Minio allows you to expose a local filestructure via the Amazon S3 API. You could, for example, use it to provide access to “buckets” (folders) of data on your filestore, secured by access/secret keys, just like AWS S3. You can further interact with your “buckets” with common tools, just as if they were hosted on S3.
Under a more advanced configuration, Minio runs in distributed mode, with features including high-availability, mirroring, erasure-coding, and “bitrot detection”.
Possible use-cases:
- Sharing files (protected by user accounts with secrets) via HTTPS, either as read-only or read-write, in such a way that the bucket could be mounted to a remote filesystem using common S3-compatible tools, like goofys. Ever wanted to share a folder with friends, but didn’t want to open additional firewall ports etc?
- Simulating S3 in a dev environment
- Mirroring an S3 bucket locally
60.1 Ingredients
- Docker swarm cluster with persistent shared storage
- Traefik configured per design
- DNS entry for the hostname you intend to use, pointed to your keepalived IP
60.2 Preparation
Setup data locations
We’ll need a directory to hold our minio file store, as well as our minio client config, so create a structure at /var/data/minio:
Prepare environment
Create minio.env, and populate with the following variables
Setup Docker Swarm
Create a docker swarm config file in docker-compose syntax (v3), something like this:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
60.3 Serving
Launch Minio stack
Launch the Minio stack by running docker stack deploy minio -c <path -to-docker-compose.yml>
Log into your new instance at https://YOUR-FQDN, with the access key and secret key you specified in minio.env.
If you created /var/data/minio
, you’ll see nothing. If you referenced existing data, you should see all subdirectories in your existing folder represented as buckets.
If all you need is single-user access to your data, you’re done!
If, however, you want to expose data to multiple users, at different privilege levels, you’ll need the minio client to create some users and (potentially) policies…
Setup minio client
To administer the Minio server, we need the Minio client. While it’s possible to download the minio client and run it locally, it’s just as easy to do it within a small (5Mb) container.
I created an alias on my docker nodes, allowing me to run mc quickly:
Now I use the alias to launch the client shell, and connect to my minio instance (I could also use the external, traefik-provided URL)
Add (readonly) user
Use mc to add a (readonly or readwrite) user, by running mc admin user add minio <access key> <secret key> <access level>
Example:
Confirm by listing your users (admin is excluded from the list):
Make a bucket accessible to users
By default, all buckets have no “policies” attached to them, and so can only be accessed by the administrative user. Having created some readonly/read-write users above, you’ll be wanting to grant them access to buckets.
The simplest permission scheme is “on or off”. Either a bucket has a policy, or it doesn’t. (I believe you can apply policies to subdirectories of buckets in a more advanced configuration)
After no policy, the most restrictive policy you can attach to a bucket is “download”. This policy will allow authenticated users to download contents from the bucket. Apply the “download” policy to a bucket by running mc policy download minio/<bucket name>
, i.e.:
Advanced bucketing
There are some clever complexities you can achieve with user/bucket policies, including:
- A public bucket, which requires no authentication to read or even write (for a public dropbox, for example)
- A special bucket, hidden from most users, but available to VIP users by application of a custom “canned policy”
Mount a minio share remotely
Having setup your buckets, users, and policies - you can give out your minio external URL, and user access keys to your remote users, and they can S3-mount your buckets, interacting with them based on their user policy (read-only or read/write)
I tested the S3 mount using goofys, “a high-performance, POSIX-ish Amazon S3 file system written in Go”.
First, I created ~/.aws/credentials, as follows:
And then I ran (in the foreground, for debugging), goofys --f -debug_s3 --debug_fuse --endpoint=https://traefik.example.com <bucketname> <local mount point>
To permanently mount an S3 bucket using goofys, I’d add something like this to /etc/fstab:
60.4 Chef’s Notes
- There are many S3-filesystem-mounting tools available, I just picked Goofys because it’s simple. Google is your friend :)
- Some applications (like NextCloud) can natively mount S3 buckets
- Some backup tools (like Duplicity) can backup directly to S3 buckets
61 Piwik
Piwik is a rich open-source web analytics platform, which can be coupled with commercial plugins for additional features. It’s most simply described as “self-hosted Google Analytics”.
61.1 Ingredients
- Docker swarm cluster with persistent shared storage
- Traefik configured per design
61.2 Preparation
Limitation of docker-swarm
The docker-swarm load-balancer is a problem for deploying piwik, since it rewrites the source address of every incoming packet to whichever docker node received the packet into the swarm. Which is a PITA for analytics, since the original source IP of the request is obscured.
The issue is tracked at #25526, and there is a workaround, but it requires running the piwik “app” container on every swarm node…
Prepare environment
Create piwik.env, and populate with the following variables
Setup docker swarm
Create a docker swarm config file in docker-compose syntax (v3), something like this:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
Setup unique static subnets for every stack you deploy. This avoids IP/gateway conflicts which can otherwise occur when you’re creating/removing stacks a lot. See my list here.
61.3 Serving
Launch the Piwik stack by running docker stack deploy piwik -c <path -to-docker-compose.yml>
Log into your new instance at https://YOUR-FQDN, and follow the wizard to complete the setup.
hero: A recipe for a sexy view of your Docker Swarm
62 Portainer
Portainer is a lightweight sexy UI for visualizing your docker environment. It also happens to integrate well with Docker Swarm clusters, which makes it a great fit for our stack.
This is a “lightweight” recipe, because Portainer is so “lightweight”. But it is shiny…
62.1 Ingredients
- Docker swarm cluster with persistent shared storage
- Traefik configured per design
- DNS entry for the hostname you intend to use, pointed to your keepalived IP
62.2 Preparation
Setup data locations
Create a folder to store portainer’s persistent data:
Setup Docker Swarm
Create a docker swarm config file in docker-compose syntax (v3), something like this:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
62.3 Serving
Launch Portainer stack
Launch the Portainer stack by running docker stack deploy portainer -c <path -to-docker-compose.yml>
Log into your new instance at https://YOUR-FQDN. You’ll be prompted to set your admin user/password.
62.4 Chef’s Notes
1. I wanted to use oauth2_proxy to provide an additional layer of security for Portainer, but the proxy seems to break the authentication mechanism, effectively making the stack so secure, that it can’t be logged into!
63 Realms
Realms is a git-based wiki (like Gollum, but with basic authentication and registration)
Features include:
- Built with Bootstrap 3.
- Markdown (w/ HTML Support).
- Syntax highlighting (Ace Editor).
- Live preview.
- Collaboration (TogetherJS / Firepad).
- Drafts saved to local storage.
- Handlebars for templates and logic.
Also of note is that the docker image is 1.17GB in size, and the handful of commits to the source GitHub repo in the past year has listed TravisCI build failures. This has many of the hallmarks of an abandoned project, to my mind.
63.1 Ingredients
- Docker swarm cluster with persistent shared storage
- Traefik configured per design
- DNS entry for the hostname you intend to use, pointed to your keepalived IP
63.2 Preparation
Setup data locations
Since we’ll start with a basic Realms install, let’s just create a single directory to hold the realms (SQLite) data:
Create realms.env, and populate with the following variables (if you intend to use an oauth_proxy to double-secure your installation, which I recommend)
Setup Docker Swarm
Create a docker swarm config file in docker-compose syntax (v3), something like this:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
Setup unique static subnets for every stack you deploy. This avoids IP/gateway conflicts which can otherwise occur when you’re creating/removing stacks a lot. See my list here.
63.3 Serving
Launch Realms stack
Launch the Wekan stack by running docker stack deploy realms -c <path -to-docker-compose.yml>
Log into your new instance at https://YOUR-FQDN, authenticate against oauth_proxy, and you’re immediately presented with Realms wiki, waiting for a fresh edit ;)
63.4 Chef’s Notes
- If you wanted to expose the Realms UI directly, you could remove the oauth2_proxy from the design, and move the traefik_public-related labels directly to the realms container. You’d also need to add the traefik_public network to the realms container.
- The inclusion of Realms was due to the efforts of @gkoerk in our Discord server. Thanks gkoerk!
64 Tiny Tiny RSS
Tiny Tiny RSS is a self-hosted, AJAX-based RSS reader, which rose to popularity as a replacement for Google Reader. It supports geeky advanced features, such as:
- Plugins and themeing in a drop-in fashion
- Filtering (discard all articles with title matching “trump”)
- Sharing articles via a unique public URL/feed
64.1 Ingredients
- Docker swarm cluster with persistent shared storage
- Traefik configured per design
64.2 Preparation
Setup data locations
We’ll need several directories to bind-mount into our container, so create them in /var/data/ttrss:
Prepare environment
Create ttrss.env, and populate with the following variables, customizing at least the database password (POSTGRES_PASSWORD and DB_PASS) and the TTRSS_SELF_URL to point to your installation.
Setup docker swarm
Create a docker swarm config file in docker-compose syntax (v3), something like this:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
Setup unique static subnets for every stack you deploy. This avoids IP/gateway conflicts which can otherwise occur when you’re creating/removing stacks a lot. See my list here.
64.3 Serving
Launch TTRSS stack
Launch the TTRSS stack by running docker stack deploy ttrss -c <path -to-docker-compose.yml>
Log into your new instance at https://YOUR-FQDN - the first user you create will be an administrative user.
64.4 Chef’s Notes
There are several TTRSS containers available on docker hub, none of them “official”. I chose x86dev’s container for its features - such as my favorite skins and plugins, and the daily automatic updates from the “rolling release” master. Some of the features of the container I use are due to a PR I submitted:
- Docker swarm looses the docker-compose concept of “dependencies” between containers. In the case of this stack, the application server typically starts up before the database container, which causes the database autoconfiguration scripts to fail, and brings up the app in a broken state. To prevent this, I include “wait-for”, which (combined with “S6_BEHAVIOUR_IF_STAGE2_FAILS=2”), will cause the app container to restart (and attempt to auto-configure itself) until the database is ready.
- The upstream git URL changed recently, but my experience of the new repository is that it’s SO slow, that the initial “git clone” on setup of the container times out. To work around this, I created my own repo, cloned upstream, pushed it into my repo, and pointed the container at my own repo with TTRSS_REPO. I don’t get the latest code changes, but at least the app container starts up. When upstream git is performing properly, I’ll remove TTRSS_REPO to revert back to the “rolling release”.
hero: Read-it-later, mate!
65 Wallabag
Wallabag is a self-hosted webapp which allows you to save URLs to “read later”, similar to Instapaper or Pocket. Like Instapaper (but not Pocket, sadly), Wallabag allows you to annotate any pages you grab for your own reference.
All saved data (pages, annotations, images, tags, etc) are stored on your own server, and can be shared/exported in a variety of formats, including ePub and PDF.
There are plugins for Chrome and Firefox, as well as apps for iOS, Android, etc. Wallabag will also integrate nicely with my favorite RSS reader, Miniflux (for which there is an existing recipe).
Here’s a video which shows off the UI a bit more.
65.1 Ingredients
- Docker swarm cluster with persistent shared storage
- Traefik configured per design
- DNS entry for the hostname you intend to use, pointed to your keepalived IP
65.2 Preparation
Setup data locations
We need a filesystem location to store images that Wallabag downloads from the original sources, to re-display when you read your articles, as well as nightly database dumps (which you should backup), so create something like this:
Prepare environment
Create wallabag.env, and populate with the following variables. The only variable you have to change is SYMFONY__ENV__DOMAIN_NAME - this must be the URL that your Wallabag instance will be available at (else you’ll have no CSS)
Now create wallabag-backup.env in the same folder, with the following contents. (This is necessary to prevent environment variables required for backup from breaking the DB container)
Setup Docker Swarm
Create a docker swarm config file in docker-compose syntax (v3), something like this:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
Setup unique static subnets for every stack you deploy. This avoids IP/gateway conflicts which can otherwise occur when you’re creating/removing stacks a lot. See my list here.
65.3 Serving
Launch Wallabag stack
Launch the Wallabag stack by running docker stack deploy wallabag -c <path -to-docker-compose.yml>
Log into your new instance at https://YOUR-FQDN, with user “wallabag” and default password “wallabag”.
Enable asynchronous imports
You’ll have noticed redis, plus the pocket/instapaper-importing containers included in the .yml above. Redis is there to allow asynchronous imports, and pocket and instapaper are there since they’re likely the most popular platform you’d want to import from. Other possibilities (you’ll need to adjust the .yml) are readability, firefox, chrome, and wallabag_v1 and wallabag_v2.
Even with all these elements in place, you still need to enable Redis under Internal Settings -> Import, via the admin user in the webUI. Here’s a screenshot to help you find it:
65.4 Chef’s Notes
- If you wanted to expose the Wallabag UI directly (required for the iOS/Android apps), you could remove the oauth2_proxy from the design, and move the traefik-related labels directly to the wallabag container. You’d also need to add the traefik_public network to the wallabag container. I found the iOS app to be unreliable and clunky, so elected to leave my oauth_proxy enabled, and to simply use the webUI on my mobile devices instead. YMMMV.
- I’ve not tested the email integration, but you’d need an SMTP server listening on port 25 (since we can’t change the port) to use it
66 Wekan
Wekan is an open-source kanban board which allows a card-based task and to-do management, similar to tools like WorkFlowy or Trello.
Wekan allows to create Boards, on which Cards can be moved around between a number of Columns. Boards can have many members, allowing for easy collaboration, just add everyone that should be able to work with you on the board to it, and you are good to go! You can assign colored Labels to cards to facilitate grouping and filtering, additionally you can add members to a card, for example to assign a task to someone.
There’s a video of the developer showing off the app, as well as a functional demo.
For added privacy, this design secures wekan behind an oauth2 proxy, so that in order to gain access to the wekan UI at all, oauth2 authentication (to GitHub, GitLab, Google, etc) must have already occurred.66.1 Ingredients
- Docker swarm cluster with persistent shared storage
- Traefik configured per design
66.2 Preparation
Setup data locations
We’ll need several directories to bind-mount into our container, so create them in /var/data/wekan:
Prepare environment
You’ll need to know the following:
- Choose an oauth provider, and obtain a client ID and secret
- Create wekan.env, and populate with the following variables
Setup Docker Swarm
Create a docker swarm config file in docker-compose syntax (v3), something like this:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
Setup unique static subnets for every stack you deploy. This avoids IP/gateway conflicts which can otherwise occur when you’re creating/removing stacks a lot. See my list here.
66.3 Serving
Launch Wekan stack
Launch the Wekan stack by running docker stack deploy wekan -c <path -to-docker-compose.yml>
Log into your new instance at https://YOUR-FQDN, with user “root” and the password you specified in gitlab.env.
66.4 Chef’s Notes
1. If you wanted to expose the Wekan UI directly, you could remove the oauth2_proxy from the design, and move the traefik-related labels directly to the wekan container. You’d also need to add the traefik network to the wekan container.
hero: Terminal in a browser, baby!
67 Wetty
Wetty is a responsive, modern terminal, in your web browser. Yes, your browser. When combined with secure authentication and SSL encryption, it becomes a useful tool for quick and easy remote access.
67.1 Why would you need SSH in a browser window?
Need shell access to a node with no external access? Deploy Wetty behind an oauth_proxy with a SSL-terminating reverse proxy (traefik), and suddenly you have the means to SSH to your private host from any web browser (protected by your oauth_proxy of course, and your OAuth provider’s 2FA)
Here are some other possible use cases:
- Access to SSH / CLI from an environment where outgoing SSH is locked down, or SSH client isn’t / can’t be installed. (i.e., a corporate network)
- Access to long-running processes inside a tmux session (like irrsi)
- Remote access to a VM / container running Kali linux, for penetration testing
67.2 Ingredients
- Docker swarm cluster with persistent shared storage
- Traefik configured per design
- DNS entry for the hostname you intend to use, pointed to your keepalived IP
67.3 Preparation
Prepare environment
Create wetty.env, and populate with the following variables per the oauth_proxy instructions:
Setup Docker Swarm
Create a docker swarm config file in docker-compose syntax (v3), something like this:
I share (with my patreon patrons) a private “premix” git repository, which includes necessary docker-compose and env files for all published recipes. This means that patrons can launch any recipe with just agit pull
and a docker stack deploy
Setup unique static subnets for every stack you deploy. This avoids IP/gateway conflicts which can otherwise occur when you’re creating/removing stacks a lot. See my list here.
67.4 Serving
Launch Wetty stack
Launch the Wetty stack by running docker stack deploy wetty -c <path -to-docker-compose.yml>
Browse to your new browser-cli-terminal at https://YOUR-FQDN. Authenticate with your OAuth provider, and then proceed to login, either to the remote host you specified (batcomputer.batcave.com, in the example above), or using user and password “term” to log directly into the Wetty alpine container (from which you can establish egress SSH)
67.5 Chef’s Notes
- You could set SSHHOST to the IP of the “docker0” interface on your host, which is normally 172.17.0.1. (Or run
/sbin/ip route|awk '/default/ { print $3 }'
in the container) This would then provide you the ability to remote-manage your swarm with only web access to Wetty. - The inclusion of Wetty was due to the efforts of @gpulido in our Discord server. Thanks Gabriel!
IV Reference
Now follows useful elements which are not full recipes.
68 OAuth proxy
Some of the platforms we use on our swarm may have strong, proven security to prevent abuse. Techniques such as rate-limiting (to defeat brute force attacks) or even support 2-factor authentication (tiny-tiny-rss or Wallabag support this).
Other platforms may provide no authentication (Traefik’s web UI for example), or minimal, un-proven UI authentication which may have been added as an afterthought.
Still platforms may hold such sensitive data (i.e., NextCloud), that we’ll feel more secure by putting an additional authentication layer in front of them.
This is the role of the OAuth proxy.
68.1 How does it work?
Normally, Traefik proxies web requests directly to individual web apps running in containers. The user talks directly to the webapp, and the webapp is responsible for ensuring appropriate authentication.
When employing the OAuth proxy , the proxy sits in the middle of this transaction - traefik sends the web client to the OAuth proxy, the proxy authenticates the user against a 3rd-party source (GitHub, Google, etc), and then passes authenticated requests on to the web app in the container.
Illustrated below:
The advantage under this design is additional security. If I’m deploying a web app which I expect only myself to require access to, I’ll put the oauth_proxy in front of it. The overhead is negligible, and the additional layer of security is well-worth it.
68.2 Ingredients
68.3 Preparation
OAuth provider
OAuth Proxy currently supports the following OAuth providers:
- Google (default)
- Azure
- GitHub
- GitLab
- MyUSA
Follow the instructions to setup your oauth provider. You need to setup a unique key/secret for each instance of the proxy you want to run, since in each case the callback URL will differ.
Authorized emails file
There are a variety of options with oauth_proxy re which email addresses (authenticated against your oauth provider) should be permitted access. You can permit access based on email domain (*@gmail.com), individual email address (batman@gmail.com), or based on provider-specific groups (i.e., a GitHub organization)
The most restrictive configuration allows access on a per-email address basis, which is illustrated below:
I created /var/data/oauth_proxy/authenticated-emails.txt, and add my own email address to the first line.
Configure stack
You’ll need to define a service for the oauth_proxy in every stack which you want to protect. Here’s an example from the Wekan recipe:
Note above how:
- Labels are required to tell Traefik to forward the traffic to the proxy, rather than the backend container running the app
- An environment file is defined, but..
- The redirect URL must still be passed to the oauth_proxy in the command argument
69 Data layout
The applications deployed in the stack utilize a combination of data-at-rest (static config, files, etc) and runtime data (live database files). The realtime data can’t be backed up with a simple copy-paste, so where we employ databases, we also include containers to perform a regular export of database data to a filesystem location.
So that we can confidently backup all our data, I’ve setup a data layout as follows:
69.1 Configuration data
Configuration data goes into /var/data/config/[recipe name], and is typically only a docker-compose .yml, and a .env file
69.2 Runtime data
Realtime data (typically database files or files-in-use) are stored in /var/data/realtime/[recipe-name], and are excluded from backup (They change constantly, and cannot be safely restored).
69.3 Static data
Static data goes into /var/data/[recipe name], and includes anything that can be safely backed up while a container is running. This includes database exports of the runtime data above.
70 Networks
In order to avoid IP addressing conflicts as we bring swarm networks up/down, we will statically address each docker overlay network, and record the details below:
Network | Range |
---|---|
Traefik | unspecified |
Docker-cleanup | 172.16.0.0/24 |
Mail Server | 172.16.1.0/24 |
Gitlab | 172.16.2.0/24 |
Wekan | 172.16.3.0/24 |
Piwik | 172.16.4.0/24 |
Tiny Tiny RSS | 172.16.5.0/24 |
Huginn | 172.16.6.0/24 |
Unifi | 172.16.7.0/24 |
Kanboard | 172.16.8.0/24 |
Gollum | 172.16.9.0/24 |
Duplicity | 172.16.10.0/24 |
Autopirate | 172.16.11.0/24 |
Nextcloud | 172.16.12.0/24 |
Portainer | 172.16.13.0/24 |
Home-Assistant | 172.16.14.0/24 |
OwnTracks | 172.16.15.0/24 |
Plex | 172.16.16.0/24 |
Emby | 172.16.17.0/24 |
Calibre-Web | 172.16.18.0/24 |
Wallabag | 172.16.19.0/24 |
InstaPy | 172.16.20.0/24 |
Turtle Pool | 172.16.21.0/24 |
MiniFlux | 172.16.22.0/24 |
Gitlab Runner | 172.16.23.0/24 |
Munin | 172.16.24.0/24 |
Bookstack | 172.16.33.0/24 |
Swarmprom | 172.16.34.0/24 |
Realms | 172.16.35.0/24 |
ElkarBackup | 172.16.36.0/24 |
Mayan EDMS | 172.16.37.0/24 |
Shaarli | 172.16.38.0/24 |
OpenLDAP | 172.16.39.0/24 |
MatterMost | 172.16.40.0/24 |
PrivateBin | 172.16.41.0/24 |
Mayan EDMS | 172.16.42.0/24 |
Hack MD | 172.16.43.0/24 |
FlightAirMap | 172.16.44.0/24 |
Wetty | 172.16.45.0/24 |
FileBrowser | 172.16.46.0/24 |
phpIPAM | 172.16.47.0/24 |
Dozzle | 172.16.48.0/24 |
KeyCloak | 172.16.49.0/24 |
Sensu | 172.16.50.0/24 |
Magento | 172.16.51.0/24 |
Graylog | 172.16.52.0/24 |
Harbor | 172.16.53.0/24 |
Harbor-Clair | 172.16.54.0/24 |
71 Introduction
Our HA platform design relies on Atomic OS, which only contains bare minimum elements to run containers.
So how can we use git on this system, to push/pull the changes we make to config files? With a container, of course!
71.1 git-docker
I made a simple container which just basically executes git in the CWD:
To use it transparently, add an alias for the “git” command, or just download it with the rest of the handy aliases:
71.2 Setup SSH key
If you plan to actually push using git, you’ll need to setup an SSH keypair. You could copy across whatever keypair you currently use, but it’s probably more appropriate to generate a specific keypair for this purpose.
Generate your new SSH keypair by running:
The output will look something like this:
Now add the contents of /var/data/git-docker/data/.ssh/id_ed25519.pub to your git account, and off you go - just run “git” from your Atomic host as usual, and pretend that you have the client installed!
72 OpenVPN
Sometimes you need an OpenVPN tunnel between your docker hosts and some other environment. I needed this to provide connectivity between swarm-deployed services like Home Assistant, and my IOT devices within my home LAN.
OpenVPN is one application which doesn’t really work in a swarm-type deployment, since each host will typically require a unique certificate/key to connect to the VPN anyway.
In my case, I needed each docker node to connect via OpenVPN back to a pfsense instance, but there were a few gotchas related to OpenVPN at CentOS Atomic which I needed to address first.
72.1 SELinux for OpenVPN
Yes, SELinux. Install a custom policy permitting a docker container to create tun interfaces, like this:
72.2 Insert the tun module
Even with the SELinux policy above, I still need to insert the “tun” module into the running kernel at the host-level, before a docker container can use it to create a tun interface.
Run the following to auto-insert the tun module on boot:
72.3 Connect the VPN
Finally, for each node, I exported client credentials, and SCP’d them over to the docker node, into /root/my-vpn-configs-here/. I also had to use the NET_ADMIN cap-add parameter, as illustrated below:
Now every time my node boots, it establishes a VPN tunnel back to my pfsense host and (by using custom configuration directives in OpenVPN) is assigned a static VPN IP.
73 Troubleshooting
Having difficulty with a recipe? Here are some tips..
73.1 Why is my stack not launching?
Run docker stack ps <stack name> --no-trunc
for more details on why individual containers failed to launching
73.2 Attaching to running container
Need to debug why your oauth2_proxy container can’t talk to its upstream app? Start by identifying which node the proxy container is running on, using docker ps <stack name>
.
SSH to the host node, and attach to the container using docker exec -it <continer id> /bin/bash
(substitute /bin/ash
for /bin/bash
, in the case of an Alpine container), and then try to telnet to your upstream host.
73.3 Watching logs of container
Need to see what a particular container is doing? Run docker service logs -f <stack name>_<container name>
to watch a particular service. As the service dies and is recreated, the logs will continue to be displayed.
73.4 Visually monitoring containers with ctop
For a visual “top-like” display of your container’s activity (as well as a detailed per-container view), try using ctop.
To execute, simply run docker run --rm -ti --name ctop -v /var/run/docker.sock:/var/run/docker.sock quay.io/vektorlab/ctop:latest
Example: