docker-web-playground

docker-web-playground

A complete Docker Compose web environment including Traefik reverse proxy, a website template (with Jekyll) and mutiple web tools (Matomo, Remark42, Prometheus, Grafana)

A complete web server built with Docker Compose. Includes Traefik, Prometheus, Grafana, Matomo, Remark42 and a Jekyll powered static website.

This project is a complete (and maybe overkill) web server environment for static websites. You can try to fully use it or to pick only a few elements. This project is not production grade. My main goal is to use it as a playground to improve my own skills and to test its integration with other web mini-projects. Any improvement suggestion will be greatly appreciated. I'm especially eager to learn on the securization and optimization aspects.


The dockerized ecosystem is composed of :

  • Traefik as a reverse proxy.
  • Prometheus to agregate metrics data.
  • Grafana to display monitoring with dashboard, for example traefik and prometheus data.
  • One or more basic static website(s) generated by Jekyll.
  • Matomo and its MariaDB database, for web analytics.
  • Remark42 to handle comments on the website(s)

I won't go into details on the reasons that made me choose one tool over another but I guess I could explain it with a few words. It's a mix between Open-Source, user personal data privacy, self-hosted solutions and ease of implementation and documentation.


A few things before we start


You are going to need a couple of things to make this work:

  • A Linux host with Docker and Docker Compose installed. Personally, I used a Windows 10 pc at home with a Ubuntu 20.04 Linux distribution installed through wsl and Docker Desktop during the development and testing phase. Afterwards, I used a Ubuntu VPS on a CSP for production.
  • A public IP address (static).
  • HTTP/HTTPS tcp ports (80 and 443) must be open to incoming traffic. If you're self-hosting at home you may have to use port forwarding and to configure your local firewall. I won't detail the process, it is well documented on the internet.
  • A DNS domain with multiple subdomains. We will assign a subdomain to each published web service (Traefik, Prometheus, Grafana, Remark42, Matomo, website) and use them to setup Traefik routing. Make sure to add the DNS records of these published services by using the DNS Manager of your DNS provider and the host public IP address. The process is usually easy and well documented but depends on your DNS provider. For example, in the case of IONOS it is described there.
  • The 2 Docker networks we will use for this project. You can create them with the following commands.
    docker network create frontend
    docker network create backend
    

The Docker proxy : Tecnativa Docker proxy


Since we will be using Traefik to automatically discover our backend services, it will require access to the docker socket. This could be a security concern as explained in the Docker Daemon Attack Surface documentation. Traefik provides a few solutions in its Docker setup documentation and I chose to use the Docker socket proxy provided by Tecnativa.

Let's create the docker-compose.yaml file at the root of our project.

version: "3.8"

services:
  dockerproxy:
    container_name: docker-proxy
    environment:
      CONTAINERS: 1
    image: tecnativa/docker-socket-proxy
    networks:
      - backend
    ports:
      - 2375
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock:ro"

networks:
  backend:
    external: true

That's it. With this configuration we have added the Docker proxy and it only allows the listing of the containers through the `CONTAINERS: 1` environment variable. Next step will be to configure Traefik to use the Docker proxy as a provider instead of the Docker socket file.


The Reverse proxy : Traefik v2


  • Create a traefik folder at the root of our project.

  • Create Traefik configuration file traefik.yaml in this folder.

    api:
      dashboard: true
      debug: false
    
    entryPoints:
      http:
        address: ":80"
      https:
        address: ":443"
    
    providers:
      docker:
        endpoint: "tcp://docker-proxy:2375"
        watch: true
        exposedbydefault: false
        network: backend
    
    log:
    filePath: "/data/traefik.log"
    format: json
    level: WARN
    
    accessLog:
    filePath: "/data/traefik_access.log"
    format: json
    
    certificatesResolvers:
      http:
        acme:
          email: [email protected]
          storage: acme.json
          httpChallenge:
            entryPoint: http
    

    We have :

    • Enabled the dashboard.
    • Created the http and https entrypoints.
    • Configured Docker provider to use the Docker proxy as endpoint and the Docker backend network per default. Prevented Traefik from exposing all containers per default (only those with the label traefik.enable=true will be exposed)
    • Enabled logs and access logs.
    • Configured a http certificate resolver so Traefik can use an ACME provider like Let's Encrypt for automatic SSL certificate generation.
  • Now we must create the empty acme.json file in the traefik folder. It will be used to keep the automatically generated SSL certificates.

    Caution! You MUST set the permissions for this file to 600. If you don't you will get a self-explanatory error in your Traefik logs. If you are on a Windows environment you may have to use wsl or your chmod 600 acme.json command won't work.

  • Next, we must add the Traefik service to the docker-compose.yaml file.

    services:
      traefik:
          depends_on:
          - dockerproxy
          image: traefik:v2.9.6
          container_name: traefik
          restart: unless-stopped
          security_opt:
          - no-new-privileges:true
          networks:
          - frontend
          - backend
          ports:
          - 80:80
          - 443:443
          volumes:
          - /etc/localtime:/etc/localtime:ro
          - ./traefik/traefik.yaml:/traefik.yaml:ro
          - ./traefik/acme.json:/acme.json
          - ./traefik/traefik_access.log:/data/traefik_access.log
          - ./traefik/traefik.log:/data/traefik.log
          labels:
          - "traefik.enable=true"
          - "traefik.http.routers.traefik.entrypoints=http"
          - "traefik.http.routers.traefik.rule=Host(`$TRAEFIK_DOMAIN`)"
          - "traefik.http.middlewares.traefik-auth.basicauth.users=(`$TRAEFIK_BASIC_AUTH`)"
          - "traefik.http.routers.traefik.middlewares=traefik-https-redirect"
          - "traefik.http.middlewares.traefik-https-redirect.redirectscheme.scheme=https"
          - "traefik.http.routers.traefik-secure.entrypoints=https"
          - "traefik.http.routers.traefik-secure.rule=Host(`$TRAEFIK_DOMAIN`)"
          - "traefik.http.routers.traefik-secure.middlewares=traefik-auth"
          - "traefik.http.routers.traefik-secure.tls=true"
          - "traefik.http.routers.traefik-secure.tls.certresolver=http"
          - "traefik.http.routers.traefik-secure.service=api@internal"
    

    I'm assuming you're already familiar with Docker and Docker Compose, that's why I will only explain the labels part. Let's see all of them :

    • traefik.enable=true : We are enabling this container.

    • traefik.http.routers.traefik.entrypoints=http : We are allowing requests from the http entrypoint defined in the config file.

    • traefik.http.routers.traefik.rule=Host($TRAEFIK_DOMAIN) : We are telling Traefik on which domain this service will respond. Here the value is defined in a separate .env environment file. I will come back to this later.

    • traefik.http.middlewares.traefik-auth.basicauth.users=($TRAEFIK_BASIC_AUTH) : We have enabled the dashboard so we are adding the Traefik's Basic Auth middleware for authentication. The value is provided by the environment file.

    • traefik.http.routers.traefik.middlewares=traefik-https-redirect : We are adding a middleware to the http router.

    • traefik.http.middlewares.traefik-https-redirect.redirectscheme.scheme=https : We are asking to the previous middleware to redirect all HTTP incoming traffic to the HTTPS entrypoint.

    • traefik.http.routers.traefik-secure.entrypoints=https : We are allowing requests from the https entrypoint defined in the config file.

    • traefik.http.routers.traefik-secure.rule=Host($TRAEFIK_DOMAIN) : We are telling Traefik on which domain the secure routing will respond.

    • traefik.http.routers.traefik-secure.middlewares=traefik-auth : We are enabling the previously created traefik-auth middleware on the secure router. This way we will be able to authenticate ourselves on the Traefik's dashboard with the appropriate credentials.

    • traefik.http.routers.traefik-secure.tls=true : We are enabling automatic TLS certificate generation.

    • traefik.http.routers.traefik-secure.tls.certresolver=http : We are telling Traefik to use the SSL certificate resolver specified in the configuration file.

    • traefik.http.routers.traefik-secure.service=api@internal : We are referencing a special service which is created automatically when the API is enabled (which is our case since we enabled the dashboard).

  • Finally we need to create the .env file I mentioned earlier at the root of the project to hold all the Compose environment variables.

    TRAEFIK_BASIC_AUTH=username:hashedpassword
    TRAEFIK_DOMAIN=your.traefik.domain.com
    

    The username:hashedpassword value can be generated with the htpasswd tool. You will need to install the apache2-utils package on your OS to use it.

    htpasswd -nb user password
    user:$apr1$sYbLhg33$u3k7DomrUjAvUYJ5inffF/
    

    Do NOT use such obvious credentials, this is an example...

    IMPORTANT : If the hashed string has any $ you will need to modify them to be $$ or Docker Compose will think it's a variable. In the given example, the final credentials value will be user:$$apr1$$sYbLhg33$$u3k7DomrUjAvUYJ5inffF/ and you will be able to authenticate yourself with the username user and the password password.


  • Okay, at this point you can try to run :

    docker-compose up -d
    

    Traefik service should start with its dashboard available at https://your.traefik.domain.com. HTTPS should be enabled, automatically generating a TLS certificate in your acme.json file. All HTTP traffic should be redirected to HTTPS.

    Note : You may encounter some weird behaviour with Traefik log and access log files. If that's the case create them manually and restart your Docker environment.


The static website


I will quickly go through the process of creating a new static website with Jekyll before showing how to integrate it to our docker-compose.yaml file. Feel free to skip the next part if you're not interested. At the end all you need is your website files into a website/_site directory (create both directories if you didn't use Jekyll).


Creating the website with Jekyll


  • Install Jekyll

    gem install bundler jekyll
    
  • Create a new Jekyll site with default theme

    In project directory execute

    jekyll new website
    
  • Generate site HTML code executing the command

    jekyll build
    

HTML generated code is under _site directory. Feel free to read Jekyll documentation for more informations.


Adding the website to our docker-compose.yaml file


We will use a basic Apache image to mount our static website in the Docker container as a bind mount of /usr/local/apache2/htdocs.

website:
  depends_on:
    - traefik
  image: httpd:2.4-alpine
  container_name: "website"
  hostname: "website"
  restart: unless-stopped
  networks:
    - backend
  volumes:
    - ./website/_site:/usr/local/apache2/htdocs/
  ports:
    - target: 80
      protocol: tcp
  labels:
    - "traefik.enable=true"
    - "traefik.http.routers.website.entrypoints=http"
    - "traefik.http.routers.website.rule=Host(`$WEBSITE_DOMAIN`)"
    - "traefik.http.middlewares.website-https-redirect.redirectscheme.scheme=https"
    - "traefik.http.routers.website.middlewares=website-https-redirect"
    - "traefik.http.routers.website-secure.entrypoints=https"
    - "traefik.http.routers.website-secure.rule=Host(`$WEBSITE_DOMAIN`)"
    - "traefik.http.routers.website-secure.tls=true"
    - "traefik.http.routers.website-secure.tls.certresolver=http"
    - "traefik.http.routers.website-secure.service=website"
    - "traefik.http.services.website.loadbalancer.server.port=80"

Do not forget to add the WEBSITE_DOMAIN value to your .env file.

TRAEFIK_BASIC_AUTH=username:hashedpassword
TRAEFIK_DOMAIN=your.traefik.domain.com
WEBSITE_DOMAIN=your.website.domain.com

Now you can try to stop our dockerized environment and restart it.

docker-compose down -v 
docker-compose up -d

Your static website should be available at https://your.website.domain.com


The metrics solution : Prometheus


Now that we have our static website up and running behind Traefik reverse proxy, let's use Prometheus to collect metrics.

  • We need to update our traefik.yaml file to enable Prometheus and set it up. The official Traefik documentation on the matter is there.

    api:
      dashboard: true
      debug: false
    
    metrics:
      prometheus:
        addRoutersLabels: true
        addEntryPointsLabels: true
        addServicesLabels: true
        entryPoint: metrics
    
    entryPoints:
      http:
        address: ":80"
      https:
        address: ":443"
      metrics:
        address: ":8080"
    
    providers:
      docker:
        endpoint: "tcp://docker-proxy:2375"
        watch: true
        exposedbydefault: false
        network: backend
    
    log:
    filePath: "/data/traefik.log"
    format: json
    level: WARN
    
    accessLog:
    filePath: "/data/traefik_access.log"
    format: json
    
    certificatesResolvers:
      http:
        acme:
          email: [email protected]
          storage: acme.json
          httpChallenge:
            entryPoint: http
    

    We have added the metrics entry point and configured Traefik to :

    • Allow Prometheus for metrics.
    • Enable metrics on routers, services and entry points.
    • Use metrics entry point to expose its metrics.

  • Now let's create a prometheus directory at the root of our project and a prometheus.yaml file inside.

    global:
      scrape_interval: 30s
      scrape_timeout: 10s
      evaluation_interval: 5s
    
    scrape_configs:
      - job_name: prometheus
          scheme: http
          static_configs:
          - targets:
              - prometheus:9090
      - job_name: traefik
          scheme: http
          static_configs:
          - targets:
              - traefik:8080
    

    With this configuration we are asking Prometheus to collect both its own metrics and Traefik metrics. This is a very basic setup, I highly recommend to read the official documentation, starting there.

  • We are going to add prometheus to our services in the docker-compose.yaml file.

    prometheus:
      image: prom/prometheus:v2.41.0
      container_name: prometheus
      restart: unless-stopped
      networks:
      - backend
      volumes:
      - ./prometheus/:/etc/prometheus/
      - /etc/localtime:/etc/localtime:ro
      - ./prometheus/prometheus.yaml:/etc/prometheus/prometheus.yaml:ro
      command:
      - "--config.file=/etc/prometheus/prometheus.yaml"
      - "--storage.tsdb.path=/prometheus"
      - "--web.console.libraries=/usr/share/prometheus/console_libraries"
      - "--web.console.templates=/usr/share/prometheus/consoles"
      ports:
      - target: 9090
        protocol: tcp
      labels:
      - "traefik.enable=true"
      - "traefik.http.routers.prometheus.entrypoints=http"
      - "traefik.http.routers.prometheus.rule=Host(`$PROMETHEUS_DOMAIN`)"
      - "traefik.http.middlewares.prometheus-https-redirect.redirectscheme.scheme=https"
      - "traefik.http.routers.prometheus.middlewares=prometheus-https-redirect"
      - "traefik.http.routers.prometheus-secure.entrypoints=https"
      - "traefik.http.routers.prometheus-secure.rule=Host(`$PROMETHEUS_DOMAIN`)"
      - "traefik.http.routers.prometheus-secure.tls=true"
      - "traefik.http.routers.prometheus-secure.tls.certresolver=http"
      - "traefik.http.routers.prometheus-secure.service=prometheus"
      - "traefik.http.services.prometheus.loadbalancer.server.port=9090"
    
  • Also add the 8080 port to traefik service configuration.

    traefik:
      ...
      ports:
        - 80:80
        - 443:443
        - 8080:8080
      ...
    
  • And don't forget to add the PROMETHEUS_DOMAIN value to your .env file!

    TRAEFIK_BASIC_AUTH=username:hashedpassword
    TRAEFIK_DOMAIN=your.traefik.domain.com
    WEBSITE_DOMAIN=your.website.domain.com
    PROMETHEUS_DOMAIN=your.prometheus.domain.com
    

Now you can try to restart everything.

docker-compose down -v 
docker-compose up -d

Prometheus should be available at https://your.prometheus.domain.com and you should see its metrics at https://your.prometheus.domain.com/metrics. There are also a lot of other possibilities available with Prometheus such as alerting etc. Check the doc!


The monitoring dashboard : Grafana


Ok we got the metrics. But maybe they would be easier to exploit with some dashboards don't you think? Let's add Grafana to our stack.

  • First create a grafana directory.

  • I'm providing an example of dasboard in this project. All you need to do is to copy the content of the provisioning folder from this repository into the grafana directory. The provided dashboard will use prometheus metrics as a datasource. You should read the Grafana official documentation on the matter.

  • You will need to create a grafana.env file into the grafana directory.

    GF_AUTH_ANONYMOUS_ENABLED=true
    GF_AUTH_BASIC_ENABLED=false
    GF_AUTH_PROXY_ENABLED=false
    GF_USERS_ALLOW_SIGN_UP=false
    GF_INSTALL_PLUGINS=grafana-piechart-panel
    

    We are overriding Grafana configuration with environment variables as explained there. This configuration is designed for a development/testing environment because it allows anonymous authentication. No login will be required to access to our dashboards.

  • Now let's add grafana service to our docker-compose.yaml

      grafana:
        image: grafana/grafana:9.3.2
        restart: unless-stopped
        container_name: grafana
        volumes:
          - ./grafana:/var/lib/grafana
          - ./grafana/provisioning:/etc/grafana/provisioning
          - /etc/localtime:/etc/localtime:ro
        env_file:
          - grafana/grafana.env
        depends_on:
          - prometheus
        networks:
          - backend
        ports:
          - target: 3000
            protocol: tcp
        labels:
          - "traefik.enable=true"
          - "traefik.http.routers.grafana.entrypoints=http"
          - "traefik.http.routers.grafana.rule=Host(`$GRAFANA_DOMAIN`)"
          - "traefik.http.middlewares.grafana-https-redirect.redirectscheme.scheme=https"
          - "traefik.http.routers.grafana.middlewares=grafana-https-redirect"
          - "traefik.http.routers.grafana-secure.entrypoints=https"
          - "traefik.http.routers.grafana-secure.rule=Host(`$GRAFANA_DOMAIN`)"
          - "traefik.http.routers.grafana-secure.tls=true"
          - "traefik.http.routers.grafana-secure.tls.certresolver=http"
          - "traefik.http.routers.grafana-secure.service=grafana"
          - "traefik.http.services.grafana.loadbalancer.server.port=3000"
    
  • Finally add the GRAFANA_DOMAIN value to your .env file!

    TRAEFIK_BASIC_AUTH=username:hashedpassword
    TRAEFIK_DOMAIN=your.traefik.domain.com
    WEBSITE_DOMAIN=your.website.domain.com
    PROMETHEUS_DOMAIN=your.prometheus.domain.com
    GRAFANA_DOMAIN=your.grafana.domain.com
    
  • And restart everything.

    docker-compose down -v 
    docker-compose up -d
    

Grafana should be up at https://your.grafana.domain.com and if you go to the Dashboards panel you should see a traefik dashboard available. Start playing!


The web analytics solution : Matomo


What about adding some web analytics solution to our website? Matomo is a free and open-source alternative to Google Analytics and this is the tool I have chosen. We will add 2 containers to our Docker environment. One for Matomo database and one for its frontend administration website.

  • First, let's create the directory structure we need in our project :

    • Create a matomo directory at the root of the project.
    • In this matomo directory create a db directory and a www-data directory. We will use those as bind mounts for Matomo persistent data (database and website).
  • Let's create Matomo's environment file db.env in the matomo directory. It will be used by both containers.

    MYSQL_ROOT_PASSWORD=rootpassword
    MYSQL_DATABASE=matomo
    MYSQL_USER=matomo
    MYSQL_PASSWORD=password
    MATOMO_DATABASE_ADAPTER=mysql
    MATOMO_DATABASE_TABLES_PREFIX=matomo_
    MATOMO_DATABASE_USERNAME=matomo
    MATOMO_DATABASE_PASSWORD=password
    MATOMO_DATABASE_DBNAME=matomo
    

    NOTE : Do not keep those default password values !

  • Now we are going to add Matomo MariaDB database service to the docker-compose.yaml file.

    db:
      image: mariadb
      container_name: mariadb
      networks:
        - backend
      command: --max-allowed-packet=64MB
      restart: always
      volumes:
        - ./matomo/db:/var/lib/mysql
      env_file:
        - ./matomo/db.env
    

    NOTE: I didn't invent anything here. This is taken from Matomo official Docker project

  • And now let's add the Matomo frontend container.

    matomo:
      depends_on:
        - db
      image: matomo
      container_name: matomo
      restart: always
      networks:
        - backend
      volumes:
        - ./matomo/www-data:/var/www/html
      environment:
        - MATOMO_DATABASE_HOST=db
      env_file:
        - ./matomo/db.env
      ports:
        - target: 80
          protocol: tcp
      labels:
        - "traefik.enable=true"
        - "traefik.http.routers.matomo.entrypoints=http"
        - "traefik.http.routers.matomo.rule=Host(`$MATOMO_DOMAIN`)"
        - "traefik.http.middlewares.matomo-https-redirect.redirectscheme.scheme=https"
        - "traefik.http.routers.matomo.middlewares=matomo-https-redirect"
        - "traefik.http.routers.matomo-secure.entrypoints=https"
        - "traefik.http.routers.matomo-secure.rule=Host(`$MATOMO_DOMAIN`)"
        - "traefik.http.routers.matomo-secure.tls=true"
        - "traefik.http.routers.matomo-secure.tls.certresolver=http"
        - "traefik.http.routers.matomo-secure.service=matomo"
        - "traefik.http.services.matomo.loadbalancer.server.port=80"
    
  • Now, don't forget to add the value for your Matomo domain in the main .env file.

    TRAEFIK_BASIC_AUTH=username:hashedpassword
    TRAEFIK_DOMAIN=your.traefik.domain.com
    WEBSITE_DOMAIN=your.website.domain.com
    PROMETHEUS_DOMAIN=your.prometheus.domain.com
    GRAFANA_DOMAIN=your.grafana.domain.com
    MATOMO_DOMAIN=your.matomo.domain.com
    
  • For the last part of the installation you will need to access the administration website which should be running after you restart you docker-environment.

    docker-compose down -v 
    docker-compose up -d
    

    If everything went well you should now be able to access Matomo at https://your.matomo.domain.com. You can now finish the installation by following the official documentation.

  • To start tracking your website you will also have to include the Matomo javascript code snippet into your website pages. I won't go into the details here as it is a trivial step and well documented.


Adding comments to your websites : Remark42


Finally, the last part of this project will be to add Remark42 which is a simple commenting engine so your website visitors can leave comments (usefull for a blog).

  • Create the following directories :

    • remark42 directory at the root of the project.
    • var directory into remark42 directory. It will be used as a docker bind mount.
  • Create the environment file .env into the remark42 directory.

    REMARK_URL=https://your.remark42.domain.com
    SECRET=<remark42_secret>
    STORE_BOLT_PATH=/srv/var/db
    BACKUP_PATH=/srv/var/backup
    SITE=<site_id>
    AUTH_ANON=true
    
    • site_id : It should be the same id than the one you will add to the Remark42 javascript code snippet which you will add to your website.
    • AUTH_ANON=true : We are allowing anonymous comment for testing purpose. You can remove this part later and add social or email logins. Read the official documentation there
  • Add remark42 service to the docker-compose.yaml file

    remark42:
      image: umputun/remark42:v1.11.2
      container_name: "remark42"
      hostname: "remark42"
      restart: unless-stopped
      networks:
        - backend
      volumes:
        - ./remark42/var:/srv/var
      ports:
        - target: 8080
          protocol: tcp
      env_file:
        - ./remark42/remark42.env
      environment:
        - TIME_ZONE=Europe/Paris
      labels:
        - "traefik.enable=true"
        - "traefik.http.routers.remark42.entrypoints=http"
        - "traefik.http.routers.remark42.rule=Host(`$REMARK42_DOMAIN`)"
        - "traefik.http.middlewares.remark42-https-redirect.redirectscheme.scheme=https"
        - "traefik.http.routers.remark42.middlewares=remark42-https-redirect"
        - "traefik.http.routers.remark42-secure.entrypoints=https"
        - "traefik.http.routers.remark42-secure.rule=Host(`$REMARK42_DOMAIN`)"
        - "traefik.http.routers.remark42-secure.tls=true"
        - "traefik.http.routers.remark42-secure.tls.certresolver=http"
        - "traefik.http.routers.remark42-secure.service=remark42"
        - "traefik.http.services.remark42.loadbalancer.server.port=8080"
        - "traefik.http.middlewares.remark42.headers.accesscontrolallowmethods=GET,OPTIONS,PUT"
        - "traefik.http.middlewares.remark42.headers.accesscontrolalloworiginlist=*"
        - "traefik.http.middlewares.remark42.headers.accesscontrolmaxage=100"
        - "traefik.http.middlewares.remark42.headers.addvaryheader=true"
    

    We have added a few extra Traefik labels here to resolve some CORS issues as explained here.


  • Add your Remark42 domain to the main .env file

    TRAEFIK_BASIC_AUTH=username:hashedpassword
    TRAEFIK_DOMAIN=your.traefik.domain.com
    WEBSITE_DOMAIN=your.website.domain.com
    PROMETHEUS_DOMAIN=your.prometheus.domain.com
    GRAFANA_DOMAIN=your.grafana.domain.com
    MATOMO_DOMAIN=your.matomo.domain.com
    REMARK42_DOMAIN=your.remark42.domain.com
    
  • Restart everything.

    docker-compose down -v 
    docker-compose up -d
    

Congratulations we are done! You should have your complete web environment up and running and you can start playing with it.

In my next project I plan to add automatized backups, a security layer for all those non-encrypted password, integrate this environment into a Kubernetes cluster and ease its deployment with Ansible. But this is another story.