Setup a new server with Docker

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

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.

1
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:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
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:

1
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.

1
sudo nano /etc/ssh/sshd_config

Find the line with `PermitRootLogin` and replace with

1
PermitRootLogin without-password

Finally restart the sshd service

1
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.

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

1
2
3
4
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`.

In order to create a new user we run

1
2
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

1
sudo usermod -aG docker <username>

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.

1
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`

1
vi /etc/ssh/sshd_config

Find these two lines and set them to no

1
2
PasswordAuthentication no
ChallengeResponseAuthentication no

Then save the file and restart the service

1
service ssh restart

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

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

1
2
3
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.

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

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
/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`.

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/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 is the easiest. This is just temporary folders where I dump backups before restoring from them.

1
2
3
4
5
6
7
8
/home/<username>
└───Restore
    ├───traefik
    │   └───temp
    ├───portainer
    │   └───temp
    └───wallabag
	└───temp

Combined the structure is as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/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.

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

>Detailed blog post

First go to the traefik folder

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

In the docker-compose file, we write:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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.

1
vi .env

Write your domain name in the `.env` file.

1
DOMAIN=<name>.<ext>

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

1
docker network create webproxy

Lastly we need the `config.toml` file. This

#begin_src toml 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 #+end_src

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

1
htpasswd -nb <username> <password>

Finally start the container

1
docker-compose up -d

> Detailed blog post

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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

1
cp ../traefik/.env .env

And we are ready to start the container.

1
docker-compose up -d

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

> Detailed blog post

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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.

> Detailed blog post

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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