After my previous posts, I wanted to setup a new server, using my new docker-compose setup and do it right this time.

SSH Keys

One of the security features I want to use, is to only allow login using SSH keys. Therefore I’m going to start generating some keys and then upload them to the server.

ssh-keygen -t rsa

This will ask you where to save the keys. I use the default location of ~/.ssh/id_rsa. Next we enter a passphrase, this could be omittet if we want the key to be all we need to login. I don’t mind the extra security, so enter some password to pair the key with. This password is needed everytime you login with the key.

The whole output looks like:

Generating public/private rsa key pair.
Enter file in which to save the key (/home/<username>/.ssh/id_rsa): 
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/<username>/id_rsa.
Your public key has been saved in /home/<username>/id_rsa.pub.
The key fingerprint is:
SHA256:kDH6Mp09kSd/T9ljc56CXSUEAhjOIYM8pcoktuOxQ <username>@<computer>
The key's randomart image is:
+---[RSA 2048]----+
|    . =.o**+.o   |
| = o + B.+.oo .  |
|= O o + * . .  . |
|oE . o * o   ....|
|o.. o + S     +o.|
|.+   o   .   . ..|
|o .              |
| o               |
|  .              |
+----[SHA256]-----+

Now we copy they key to the server, this can be done using:

cat ~/.ssh/id_rsa.pub | ssh root@<server-ip> "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >>  ~/.ssh/authorized_keys"

When you now login using ssh root@<server-ip> it will ask you for your passphrase which is the passphrase you used when generating the key.

Logging in to root I can disable login uisng root+password.

sudo nano /etc/ssh/sshd_config

Find the line with PermitRootLogin and replace with

PermitRootLogin without-password 

Finally restart the sshd service

sudo systemctl reload sshd.service

Trying to login using root + <root password> will result in nothing, whereas using your ssh keys will allow you to login.

Setup Ubuntu

Updating

My server runs Docker on Ubuntu so before I start installing a bunch of things, let’s update it.

apt-get update
apt-get upgrade
do-release-upgrade
sudo apt install apache2-utils

Now everything is up to date and we can create a new user to login with, so I don’t do everything using root.

New User

In order to create a new user we run

adduser <username>
usermod -aG sudo <username>

Now my regular user can use sudo so I don’t have to switch to root all the time.

Next, I want to avoid using sudo docker <cmd> and just be able to run docker commands without sudo. This can be done by writing

sudo usermod -aG docker <username>

SSH keys

Right now we can use ssh keys to login to root but logging in to <username> does not require keys.

First copy the keys generated previously to the new user.

cat ~/.ssh/id_rsa.pub | ssh <username>@<server-ip> "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >>  ~/.ssh/authorized_keys" 

Login using root and ssh keys and edit sshd_config

vi /etc/ssh/sshd_config

Find these two lines and set them to no

PasswordAuthentication no
ChallengeResponseAuthentication no

Then save the file and restart the service

service ssh restart

We are not forced to login to the server using the ssh keys and cannot login using passwords.

Docker-Compose

Installing docker-compose is pretty easy. First I made the mistake of using apt-get install docker-compose, which giSo starting over I went to the docker website and followed

sudo curl -L "https://github.com/docker/compose/releases/download/1.22.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose --version

Now the version is correct and I could continue without problems.

Folder structure

I like to keep a simple folder structure for my Docker containers. This is easy to keep track of local data and backups.

Docker

Let’s go through them one at a time and then see it all together. For Docker containers I don’t like using volumes to store data. I prefer to keep it in folders in my docker-folder.

/home/<username>
└───Docker
    ├───traefik
    │   │   docker-compose.yml
    │   └───config
    │
    ├───portainer
    │   │   docker-compose.yml
    │   └───data
    │
    └───wallabag
        │   docker-compose.yml
        ├───data
        └───images

Above is an example of three containers (traefik, portainer and wallabag) and their folder structure. It is pretty simple, since with one main folder for each container, inside the folder are the subfolders that the container uses and the docker-compose file.

From previous blog posts, I know that traefik has one folder called config, portainer have a data folder and wallabag has two folders called data and images.

Backup

For backups it is about the same structure, except there is another nested folder for each backup. The backups are named for the date and time.

/home/<username>
└───Backup
    ├───traefik
    │   │   traefik.sh
    │   ├───<date>
    │   │   │   docker-compose.yml
    │   │   └───config
    │   └───<date>
    │       │   docker-compose.yml
    │       └───config
    │
    ├───portainer
    │   │   portainer.sh
    │   ├───<date>
    │   │   │   docker-compose.yml
    │   │   └───data
    │   └───<date>
    │       │   docker-compose.yml
    │       └───data
    │
    └───wallabag
        │   wallabag.sh
        ├───<date>
        │   │   docker-compose.yml
        │   ├───data
        │   └───images
        └───<date>
            │   docker-compose.yml
            ├───data
            └───images

Each blog posts about containers should have backup scripts for their containers.

Restore

Restore is the easiest. This is just temporary folders where I dump backups before restoring from them.

/home/<username>
└───Restore
    ├───traefik
    │   └───temp
    ├───portainer
    │   └───temp
    └───wallabag
        └───temp

Combined

Combined the structure is as follows:

/home/<username>
├───Docker
│   ├───traefik
│   │   │   docker-compose.yml
│   │   └───config
│   │
│   ├───portainer
│   │   │   docker-compose.yml
│   │   └───data
│   │
│   └───wallabag
│       │   docker-compose.yml
│       ├───data
│       └───images
│
├───Backup
│   ├───traefik
│   │   │   traefik.sh
│   │   ├───<date>
│   │   │   │   docker-compose.yml
│   │   │   └───config
│   │   └───<date>
│   │       │   docker-compose.yml
│   │       └───config
│   │
│   ├───portainer
│   │   │   portainer.sh
│   │   ├───<date>
│   │   │   │   docker-compose.yml
│   │   │   └───data
│   │   └───<date>
│   │       │   docker-compose.yml
│   │       └───data
│   │
│   └───wallabag
│       │   wallabag.sh
│       ├───<date>
│       │   │   docker-compose.yml
│       │   ├───data
│       │   └───images
│       └───<date>
│           │   docker-compose.yml
│           ├───data
│           └───images
└───Restore
    ├───traefik
    │   └───temp
    ├───portainer
    │   └───temp
    └───wallabag
        └───temp

This might seem like a lot, but it makes everything a lot easier to work with compared to using volumes.

Containers

Let’s run through some containers docker-compose file, although more info can be found in their respective blog posts.

Traefik

Detailed blog post

First go to the traefik folder

cd ~/Docker/traefik
vi docker-compose.yml

In the docker-compose file, we write:

version: "3"
services:
  traefik:
    container_name: traefik
    image: traefik:alpine
    ports:
      - 80:80
      - 443:443
    volumes:
      - ${PWD}/config:/etc/traefik
      - /var/run/docker.sock:/var/run/docker.sock
    labels:
      - "traefik.enable=true"
      - "traefik.frontend.rule=Host:traefik.${DOMAIN}"
      - "traefik.port=8080"
networks:
  default:
    external:
      name: webproxy

Here we setup all the needed information to use Traefik and Let’s Encrypt. Notice how in the frontend.rule i added ${DOMAIN} instead of a my domain name. This is because I am using an environment file.

vi .env

Write your domain name in the .env file.

DOMAIN=<name>.<ext>

We are not using the default network, so we have to create it before we can use it.

docker network create webproxy

Lastly we need the config.toml file. This

defaultEntryPoints = ["http", "https"]

[entryPoints]
  [entryPoints.http]
  address = ":80"
  [entryPoints.https]
  address = ":443"
    [entryPoints.https.tls]
  [entryPoints.http.redirect]
  entryPoint = "https"
  [entryPoints.web]
  address = ":8080"
    [entryPoints.web.auth]
      [entryPoints.web.auth.basic]
        users = ["<username>:$apr1$9WtvIi9R$7UmmK6YEs0dDtLlM.1sbh."]
[api]
entryPoint = "web"

[acme]
email = "xxx@xxx.com"
entryPoint = "https"
storage = "acme.json"
onHostRule = true
  [acme.httpChallenge]
  entryPoint = "http"

[docker]
endpoint = "unix:///var/run/docker.sock"
domain = "domain.com"
exposedByDefault = false

Notice how we have a username and hashed password for our authentication. This is done using htpasswd

htpasswd -nb <username> <password>

Finally start the container

docker-compose up -d

Portainer

Detailed blog post

For Portainer, it’s a simple small container. Create the docker-compose file

version: "3"

services:
  portainer:
    container_name: portainer
    image: portainer/portainer
    command: -H unix:///var/run/docker.sock
    volumes:
      - ${PWD}/data:/data
      - /var/run/docker.sock:/var/run/docker.sock
    labels:
      - "traefik.enable=true"
      - "traefik.frontend.rule=Host:portainer.${DOMAIN}"
      - "traefik.port=9000"

networks:
  default:
    external:
      name: webproxy

Then copy the .env file

cp ../traefik/.env .env

And we are ready to start the container.

docker-compose up -d

Go to portainer.<domain>.<ext> and create an admin user. Then login and you are good to go.

Wallabag

Detailed blog post

version: "3"
services:
  wallabag:
    container_name: wallabag
    image: wallabag/wallabag
    volumes:
      - ${PWD}/data:/var/www/wallabag/data
      - ${PWD}/images:/var/www/wallabag/web/assets/images
    environment:
      - SYMFONY__ENV__FOSUSER_REGISTRATION=false
      - SYMFONY__ENV__DOMAIN_NAME=https://wallabag.${DOMAIN}
      - SYMFONY__ENV__FOSUSER_CONFIRMATION=false
    labels:
      - "traefik.enable=true"
      - "traefik.frontend.rule=Host:wallabag.${DOMAIN}"
networks:
  default:
    external:
      name: webproxy

Open the wallabag site and create a new user. Afterwards you can install the firefox extension or the iOS app if needed.

Cypht

Detailed blog post

version: "3"

services:
  cypht_app:
    container_name: cypht_app
    image: sailfrog/cypht-docker:latest
    environment:
      - CYPHT_AUTH_USERNAME=${USER}
      - CYPHT_AUTH_PASSWORD=${PASS}
      - CYPHT_DB_CONNECTION_TYPE=host
      - CYPHT_DB_HOST=cypht_db:3306
      - CYPHT_DB_NAME=${DB}
      - CYPHT_DB_USER=${DB_USER}
      - CYPHT_DB_PASS=${DB_PASS}
      - CYPHT_ALLOW_EXTERNAL_IMAGE_SOURCES=true
      - CYPHT_MODULE_NASA=enable
      - CYPHT_DISABLE_IP_CHECK=true
    volumes:
      - ${PWD}/users:/var/lib/hm3/users
      - ${PWD}/app_data:/var/lib/hm3/app_data
    labels:
      - "traefik.enable=true"
      - "traefik.frontend.rule=Host:mail.${DOMAIN}"
    depends_on:
      - cypht_db
  cypht_db:
    container_name: cypht_db
    image: mariadb:10
    ports:
      - 3307:3306
    environment:
      - MYSQL_ROOT_PASSWORD=${ROOT_PASS}
      - MYSQL_DATABASE=${DB}
      - MYSQL_USER=${DB_USER}
      - MYSQL_PASSWORD=${DB_PASS}
    volumes:
      - ${PWD}/mysql:/var/lib/mysql

networks:
  default:
    external:
      name: webproxy