Minimum effort, maximum personal cloud infrastructure

Table of Contents


I never really enjoy blogs with paragraphs of philosophical explanations of why one should use the presented solution so I’ll get down to business right away and address some of the philosophy later.


To create as much high quality personal (cloud) infrastructure with as little effort as possible.

I’ll focus on getting Nextcloud running as minimum viable personal cloud infrastructure (MVPCI). The methods used to get Nextcloud running are easily extended to other services as well, I’ll present some examples later on.


I recently bough new home-server hardware, the goal was to strike a balance between power and power consumption. These are the specs:

Part Model Price Date purchased
CPU Intel Core i3-9100 Boxed €122.32 2020-12-15
Motherboard Fujitsu D3644-B €157.45 2020-12-15
RAM Crucial CT2K8G4DFS824A €57.45 2020-12-15
Power Supply be quiet! Pure Power 11 400W €53.91 2020-12-15
SSD Samsung 970 EVO 1TB €125 2020-12-15
Case Fractal Design Define R3 €93.50 2010-12-23
Total €609.63

The URLs link to’s price comparison section, this is a popular comparison site for hardware in the Netherlands. I didn’t include any cables, since the motherboard is for OEMs, it does not come with any but I had them lying around from my previous server build.



I’ll use Ubuntu Desktop because I can get it up and running within 10 min and so far it has worked flawlessly on any hardware I tried it on (and indeed it works perfectly with the hardware mentioned above). Ubuntu Desktop presents a desktop so I can easily open some terminals and drag some files around and use VNC to do the same after I stowed the server away in the basement. But since we are going to use Docker Compose, it really does not matter which distribution you choose; after you booted it, everything will be the same as described here.

Docker Compose

When I first tried to use Docker for my personal infrastructure, I concluded that it made things more complicated for me. When I first heard the term Docker Compose, I thought “hmm, another layer of complexity”… But no, Docker Compose = Docker for Dummies. One can simply write down (in Yaml) what services one wants and Docker Compose will download them down and make them run.

Traefik proxy

Traefik is a reverse proxy and can seamlessly integrate Let’s Encrypt to get you valid certificates for your (sub) domains. It can be fully configured from within a Docker Compose yaml file and allows you to run multiple services in the same port, routing requests based on the contacted (sub) domain.

VSCode (optional)

Whenever I work on a server, I use VSCode with the remote-ssh plugin, then I can open files on the remote server and start using them in a ssh session directly. I highly recommend it. Does require you apt install openssh-server on the server.

Building your personal infrastructure

Getting the Linux Server up and running

Pick whatever GNU/Linux distribution you prefer, and install it on your hardware. I have used Ubuntu Desktop so if you don’t, some small things may be different.

Install Docker and Docker Compose

Both Docker and Docker Compose are best installed not from your distributions repositories (unless you run Arch maybe) but using direct methods. First we install Docker (instructions optimized for Ubuntu 20.04) (source:, run the following commands in succession, see the source for more details

sudo apt update

sudo apt install apt-transport-https ca-certificates curl software-properties-common

curl -fsSL | sudo apt-key add -

sudo add-apt-repository "deb [arch=amd64] focal stable"

sudo apt update

apt-cache policy docker-ce

sudo apt install docker-ce

sudo usermod -aG docker ${USER}

su - ${USER}

Verify that you can use docker by issuing the following command, you should see a version number printed

docker --version

Now we install Docker Compose

sudo curl -L "$(uname -s)-$(uname -m)" -o /usr/local/bin/


sudo chmod +x /usr/local/bin/docker-compose

Verify that you can use docker-compose by issuing the following command, you should see a version number printed

docker-compose --version

Running Nextcloud

Make a new directory, the name is not important, I usually use “docker”

mkdir docker

Open a text file called docker-compose.yaml and paste the following content into it:

version: "3.9"
    image: traefik:latest
    container_name: traefik
      - --log.level=DEBUG
      - --api.insecure=true
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
      - --certificatesresolvers.mytlschallenge.acme.tlschallenge=true
      - --serversTransport.insecureSkipVerify=true
      - 80:80
      - 443:443
      - 8888:8080
      - ./letsencrypt:/letsencrypt
      - /var/run/docker.sock:/var/run/docker.sock:ro

    container_name: nextcloud_db
    image: mariadb
    restart: always
    command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
      - ./
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud

    container_name: nextcloud_app
    image: nextcloud
    restart: always
      - nextcloud_db
      - ./
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud
      - MYSQL_HOST=nextcloud_db
      - traefik.enable=true
      - traefik.http.routers.nextcloud_app-http.entrypoints=web
      - traefik.http.routers.nextcloud_app-http.rule=Host(``)
      - traefik.http.routers.nextcloud_app-http.middlewares=nextcloud_app-https
      - traefik.http.middlewares.nextcloud_app-https.redirectscheme.scheme=https
      - traefik.http.routers.nextcloud_app.entrypoints=websecure
      - traefik.http.routers.nextcloud_app.rule=Host(``)
      - traefik.http.routers.nextcloud_app.tls=true
      - traefik.http.routers.nextcloud_app.tls.certresolver=mytlschallenge

But before we can use this we need to do some things.

Save the modified file. Now we are ready to pull down and start Nextcloud by issuing:

docker-compose up -d

You will see the containers being pulled down. When they are started, visit your Nextcloud install at (the domainname you choose).

Tweaking Nextcloud

When you visit your domain, you’ll find Nextcloud ready for intialization. I ran into some issues going through this process which I’d like to warn you about.

The first one is that I initially choose to “install recommended apps”, this means that after setting up the database and the admin user, Nextcloud will install several extra apps. For me choosing this option repeatedly led to a very slow Nextcloud install, really unuseable. So I deselected this option and found a fast and snappy Nextcloud install waiting for me.

The second is that Nextcloud requires a form of very regular maintenance, regular as in every 5 min. There are several ways to get this done, if you click your avatar in the top left of your Nextcloud install, then choose “Settings” and the “Basic settings” (under Adminstration), the methods are listed. The best method is to use Cron, you can have your server run the required cron job every 5 min by adding the following line to your crontab (open crontab by typing crontab -e on the command line):

*/5 * * * * docker exec -u www-data nextcloud_app php cron.php

My third problem was that I had issues connecting my sync apps, they are described here, I hope you will not run into them, otherwise, read this thread.

And then we have a forth issue, to set up caldav and carddav correctly for some clients we need to add some configuration to resolve so called well-known caldav and carddav. Here is some more information: I hope you can use the Traefik 2 examples to avoid having to edit .htaccess manually. I did do that however, I changed these 2 lines (I added the part):

  RewriteRule ^\.well-known/carddav [R=301,L]
  RewriteRule ^\.well-known/caldav [R=301,L]

More services, more examples

You can add the following snippets to your docker-compose.yaml file to get more services.

A static website

This service serves you this very website (replace with a (sub) domain of your choosing).

    image: nginx
    container_name: nginx_static_site
      - ./nginx:/usr/share/nginx/html
      - traefik.enable=true
      - traefik.http.routers.nginx_static_site-http.entrypoints=web
      - traefik.http.routers.nginx_static_site-http.rule=Host(``)
      - traefik.http.routers.nginx_static_site-http.middlewares=nginx_static_site-https
      - traefik.http.middlewares.nginx_static_site-https.redirectscheme.scheme=https
      - traefik.http.routers.nginx_static_site.entrypoints=websecure
      - traefik.http.routers.nginx_static_site.rule=Host(``)
      - traefik.http.routers.nginx_static_site.tls=true
      - traefik.http.routers.nginx_static_site.tls.certresolver=mytlschallenge

Home Assistant and Mosquitto

This software controls many things in my home. Make sure you create /mosquitto/config/mosquitto.conf, to use the persistant storage of data and logs, add these line to said file:

persistence true
persistence_location /mosquitto/data/
log_dest file /mosquitto/log/mosquitto.log

Then add the following snippet to your docker-compose.yaml file:

    container_name: home-assistant
    image: homeassistant/home-assistant:stable
      - ./homeassistant:/config
      - TZ=Europe/Amsterdam
    restart: always
    network_mode: host

    container_name: mosquitto
    image: eclipse-mosquitto
      - ./mosquitto/data:/mosquitto/data
      - ./mosquitto/log:/mosquitto/log
      - ./mosquitto/mosquitto.conf:/mosquitto/config/mosquitto.conf
      - 1883:1883
      - 9001:9001

and run docker-compose up -d.

The Unify controller

This is the controller software for a Ubiquiti Unify network. I use it to control my home network. I don’t use it with Traefik (yet).

    image: linuxserver/unifi-controller
    container_name: unifi-controller
      - PUID=1000
      - PGID=1000
      - MEM_LIMIT=1024M #optional
      - ./unify-controller:/config
      - 3478:3478/udp
      - 10001:10001/udp
      - 8080:8080
      - 8443:8443
      - 1900:1900/udp #optional
      - 8843:8843 #optional
      - 8880:8880 #optional
      - 6789:6789 #optional
      - 5514:5514 #optional
    restart: unless-stopped
      - traefik.enable=false   

A MineCraft server

My son likes MineCraft, I like servers…

    image: itzg/minecraft-server
    container_name: minecraft
     - 25565:25565
    #  - 19132:19132/udp # Enable this port to use with GeyserMC so Bedrock clients can join too.
     - ./mc-paper/data:/data
     - ./mc-paper/plugins:/plugins
      EULA: "TRUE"
      TYPE: "PAPER"
      MEMORY: "4G"
      TZ: Europe/Amsterdam
      MODE: creative
      MOTD: Bedrock vs Java
    #   OPS: your minecraft id
    tty: true
    stdin_open: true
    restart: always


For feedback, you can sent me an email at pci at hmrt dot nl.