Objectives

You're a developer. You like to do things by yourself. You want to build your own microservices farm (either you mind about using exinsting online services for security purpose, either to get some knowledge about it, either ... anyway, you've got reasons.), but you're alone, poor little developer, and you quickly figured out the task was heavy time consuming, especially when you could have leisure instead.

I don't pretend to be a master on the containerization art, but here there is an efficient little setup to build your technical stacks and expose any service having a web-based interface (over http(s)), with autogenerated and autorenewed Let's Encrypt SSL certificates, to achieve what matters the most for you: DEVELOPMENT !

The other objective is to be able to migrate easily your server since every 2 or 3 years, low-cost dedicated servers gets renewed (in France at least), and you get generously notified 3 to 6 months before the shutdown you'll get a brand new server from the same price range, still you'll lose it all if you don't make a backup so you better hurry up.

Architecture

I'll present here a single server architecture (again poor little developer, you don't have much money, and you don't want to spend too much on that), I didn't experimented clustering yet (not needed at the moment).

The reciepes are docker-compose based (I'll assume you already know how to install/use docker and docker-compose. If you don't, there are already tons of resources about it on the web).

Don't forget to create yourself an user (never use root for your apps). You'd better write the UID and GID of your user somewhere (usually 1000:1000 for the first user you create) because we will need them later on.

Let's take a look at our folder tree:

```bash {.line-numbers} $ tree ~ /home/your_user └── docker ├── images │   ├── service1 │   │   ├── docker-compose.yml │   │   └── volumes -> ../../volumes/service1 │   └── service2 │   ├── docker-compose.yml │   └── volumes -> ../../volumes/service2 └── volumes ├── service1 │   └── config.conf └── service2 └── env.ini


Folders `images` et `volumes` gets separated:
* `images` will contain all docker-compose files, environment variables, other files that could get versioned, etc.
* `volumes` will contain the persistant data of your containers

We centralise all the volumes in a single folder so you can mount your data on a different location (or multiple different locations). This will also speed up the things when uploading the data to another server.
The content of the folder `images` can easily gets versioned, 1 or multiple repositories again, up to you.

## Realisation

### Setup of the reverse proxy

#### docker-compose.yml
Path: ~/docker/images/reverseproxy/docker-compose.yml

```yaml .line-numbers
version: '3'

networks:
  reverseproxy:
     driver: "bridge"

services:
  nginx-proxy:
    restart: "unless-stopped"
    image: "jwilder/nginx-proxy:alpine"
    ports:
        - "80:80"
        - "443:443"
    volumes:
        - "./volumes/certs:/etc/nginx/certs:ro"
        - "./volumes/vhosts:/etc/nginx/vhost.d"
        - "./volumes/html:/usr/share/nginx/html"
        - "./volumes/htpasswd:/etc/nginx/htpasswd:ro"
        - "/var/run/docker.sock:/tmp/docker.sock:ro"
    networks:
        - "reverseproxy"
    labels:
        - "com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy"

  nginx-proxy-companion:
    restart: "unless-stopped"
    image: "jrcs/letsencrypt-nginx-proxy-companion"
    volumes:
        - "./volumes/certs:/etc/nginx/certs:rw"
        - "./volumes/vhosts:/etc/nginx/vhost.d"
        - "./volumes/html:/usr/share/nginx/html"
        - "/var/run/docker.sock:/var/run/docker.sock"
    networks:
        - "reverseproxy"

Explanations

networks:
  reverseproxy:
     driver: "bridge"

This network will be shared amongst all other containers (but that will hopefully be declarative)

    ports:
        - "80:80"
        - "443:443"

Our reverse proxy is our main entrypoint, we map the ports 80 and 443 of the host. It means you won't be able to map these ports againt, but that's all the purpose of it ... The port 80 is definetly needed for Let's Encrypt.

    labels:
        - "com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy"

==== UNFINISHED TRANSLATION ====

C'est ce qui fait le lien avec nginx-proxy-companion. Si vous n'avez pas besoin de Let's Encrypt (j'espère quand même que vous n'exposerez pas vos services de prod en http), vous pouvez virer cette ligne, ainsi que le service nginx-proxy-companion.

 nginx-proxy-companion:

C'est le petit container magique qui va générer et regénérer automatiquement vos certificats ssl. Notez bien sûr que pendant le renouvellement, votre application sera indisponible, car Let's Encrypt a besoin de dialoguer avec le reverse proxy pour vérifier que votre domaine est bien mappé sur votre serveur.

Setup d'un autre service (par exemple, une stack nginx/php toute simple)

docker-compose.yml

```yaml .line-numbers version: '3'

networks: reverseproxy: external: name: "reverseproxy_reverseproxy"

services: web: image: nginx restart: "unless-stopped" volumes:

  • "./volumes/logs:/var/log/nginx"
  • "./volumes/conf/nginx.conf:/etc/nginx/nginx.conf"
  • "./volumes/sources:/mnt/sources" environment:
  • VIRTUAL_HOST=domain.com
  • LETSENCRYPT_HOST=domain.com
  • LETSENCRYPT_EMAIL=mail@domain.com depends_on:
  • php networks:
  • "default"
  • "reverseproxy"

    php: image: php:fpm-alpine restart: "unless-stopped" volumes:

  • "./volumes/php/php.ini:/usr/local/etc/php/php.ini"
  • "./volumes/sources:/mnt/sources" networks:
  • "default"

Explications

networks:
  reverseproxy:
    external:
      name: "reverseproxy_reverseproxy"

On importe le network qu'on a créé dans notre docker compose précédent. Le nom est reverseproxy_reverseproxy parce que le docker-compose.yml de mon reverse proxy se trouve dans le dossier reverseproxy (en remplacement de service1 dans l'arborescence exemple) et parce que la clé de la définition du network dans ce même fichier est reverseproxy. A vous d'adapter en conséquence si vous modifier ces valeurs. Cela veut également dire qu'il vaudrait mieux lancer votre reverse proxy docker-compose up -d avant de lancer vos autres services, au moins une fois, histoire de créer le network.

  environment:
      - VIRTUAL_HOST=domain.com
      - LETSENCRYPT_HOST=domain.com
      - LETSENCRYPT_EMAIL=mail@domain.com

Ahhh, on arrive enfin à la partie croustillante (tout ça pour ça !).

La variable VIRTUAL_HOST sera hookée par le reverse proxy pour TOUS les containers qui l'auront définie. C'est possible grâce à l'API docker parce qu'on a partagé le socket docker du host dans le container du reverse proxy:

    volumes:
        - "/var/run/docker.sock:/tmp/docker.sock:ro"

Ce hook va s'opérer lors du démarrage du container du reverse proxy, mais aussi lors du démarrage d'un container contenant cette variable d'environement. Ce qui veut dire que vous n'avez pas à redémarrer le reverse proxy à chaque création de service. Génial Oh pu**** c'est génial.

Vous pouvez bien sûr mettre un sous domaine, et même plusieurs domaines, en les séparant par des , virgules. Le contenu de LETSENCRYPT_HOST doit être identique à celui de VIRTUAL_HOST (sauf si certains domaines/sous-domaines ne doivent pas être soumis à Let's Encrypt). LETSENCRYPT_EMAIL, c'est l'adresse email qui sera associée aux certificats générés. Il me semble qu'avec des virgules et si votre container est en domaines multiples, vous pouvez donner des adresses email différentes pour vos différents domaines, mais j'ai jamais testé. LETSENCRYPT_HOST et LETSENCRYPT_EMAIL sont bien sûr optionnels (genre, sur une machine de dev, vous en voulez pas).

  networks:
      - "default"
      - "reverseproxy"

Il faudra mettre le network reverseproxy (ou le petit nom que vous lui aurez donné si vous le changez) partout où une variable d'environnement VIRTUAL_HOST est définie. Quant au network "default", il s'agit du nom du network qui est normalement mis en place pour tous les services définis dans un docker-compose.yml, donc histoire de pas perdre cette feature, il faut le remettre (sinon plus de communcation entre vos containers).

Notez que le network reverseproxy n'a pas été mis sur le service php, il n'en a pas besoin.

Avec ce setup cependant, vos containers communiquent en HTTP avec le reverse proxy (port 80 par défaut donc, ou vous pouvez aussi définir la variable d'environement VIRTUAL_PORT), il peut s'agir d'un problème de sécurité si vous êtes absolument psychopathe.


Blog Comments powered by Isso.