{"id":16961089,"url":"https://github.com/ricsanfre/public-websites-docker","last_synced_at":"2025-10-20T08:05:22.781Z","repository":{"id":54639106,"uuid":"451902787","full_name":"ricsanfre/public-websites-docker","owner":"ricsanfre","description":"Selfhosting personal static websites using private web analytics and comments platforms","archived":false,"fork":false,"pushed_at":"2022-09-25T17:27:28.000Z","size":73,"stargazers_count":14,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-11T22:11:33.279Z","etag":null,"topics":["comments-system","docker","docker-compose","jekyll","letsencrypt","matomo","personal-website","remark42","self-hosted","static-website","traefik","web-analytics"],"latest_commit_sha":null,"homepage":"","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ricsanfre.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-01-25T14:09:45.000Z","updated_at":"2024-11-16T07:06:40.000Z","dependencies_parsed_at":"2023-01-18T22:01:21.596Z","dependency_job_id":null,"html_url":"https://github.com/ricsanfre/public-websites-docker","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ricsanfre/public-websites-docker","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ricsanfre%2Fpublic-websites-docker","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ricsanfre%2Fpublic-websites-docker/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ricsanfre%2Fpublic-websites-docker/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ricsanfre%2Fpublic-websites-docker/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ricsanfre","download_url":"https://codeload.github.com/ricsanfre/public-websites-docker/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ricsanfre%2Fpublic-websites-docker/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280050916,"owners_count":26263885,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-20T02:00:06.978Z","response_time":62,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["comments-system","docker","docker-compose","jekyll","letsencrypt","matomo","personal-website","remark42","self-hosted","static-website","traefik","web-analytics"],"created_at":"2024-10-13T22:50:59.188Z","updated_at":"2025-10-20T08:05:22.740Z","avatar_url":"https://github.com/ricsanfre.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Selfhosting personal static websites powered by private web analytics and private comments platform\n\nThis project shows how to configure a selfhosted server with internet access for selfhosting our static websites/blogs (for example created with [Jekyll](https://jekyllrb.com/) along with dynamic web services providing the capabilities to enable comments within our static sites and to track the number of visitors or the most viewed pages in our website.\n\nThis project enables to automatically deploy using Docker the following components:\n\n  - [Traefik](traefik.io) as HTTP/HTTPS reverse proxy. Traefik is the front-end for all backend web services\n  - [remark42](https://remark42.com/) as commenting platform for supporting comments in our posts\n  - [matomo](https://matomo.org/) as web analytics platform for tracking visitors in our websites.\n  - Personal static website, automatically generated with Jekyll and exposed by a static HTTP server like nginx or apache. As alternative personal websites can be hosted in third party static web hosting provider like Github Pages.\n\n**Why Docker**\n\nDocker, as container platform, enables the portability of the software between different hosting environments (bare metal, VM, etc.), so any kind of selfhosted platform can be used: a VM running on a Cloud Service Provider or a baremetal server with internet access like a Raspberry PI.\n\n**Why Traefik**\n\nFor securing the access through HTTPS using SSL certificates, Traefik will be used.\n\nTraefik is a Docker-aware reverse proxy with a monitoring dashboard. Traefik also handles setting up your SSL certificates using [Let’s Encrypt](https://letsencrypt.org/) allowing you to securely serve everything over HTTPS. Docker-aware means that Traefik is able to discover docker containers and using labels assigned to those containers automatically configure the routing and SSL certificates to each service. See Traefik documentation about [docker provider](https://doc.traefik.io/traefik/providers/docker/).\n\n**Why Matomo**\n\nMatomo is a selfhost alternative to Google Analytics service. It provides a better way to protect user's data privacy (user's data is not shared with any third party) and it can work in cookieless mode.\n\n**Why remark42**\n\nRemark is a seflhost alternative to other comments platforms (Disqus, Commento) that is free. It also provide a better way to protect user's data privacy and it enables social login (via Google, Twitter, Facebook, Microsoft, GitHub, Yandex, Patreon and Telegram) or post anonymous comments.\n\n## Requirements\n\nFor selfhosting your websites you need:\n\n- DNS domain owned by you. Different DNS subdomains need to be assigned to each of the published web services (matomo, remark42, personal website). Traefik rules will use the DNS domain information to route the HTTP/HTTPS traffic to the proper backend web service.\n- Linux VM hosted in a Public Cloud Service Provider, with associated public IP address.\n- Linux VM or baremetal server hosted by you in your home network. In this case you will use the Public IP address assigned by your ISP.\n\n## Enabling Internet Access\n\nTraefik front end need to be accesible from the Internet. Incoming HTTP/HTTPS (tcp ports 80 and 443) traffic need to be enabled and so the server.\n\n### Using Cloud Service Provider\n\nIn case of using a Cloud Service Provided, the IP address assigned to the VM for hosting the websites need to be created wih an external IP (public IP address) and the corresponding security rules (i.e.: security groups) need to be configured to enable the incoming HTTP/HTTPS traffic.\n\n### Selfhosting at home network\n\nAt home usually the ISP provide a public IP address to your home router (GPON or ADSL router) and the router provide internet access to your home network via NAT. Incoming traffic on HTTP/HTTPS ports for your home network is usually blocked by the home router. \n\nHome router port forwarding must be enabled in order to reach a host in your home network from Internet.\nTraffic incoming to ports 80 (HTTP) and 443 (HTTPS) will be redirected to the IP address of the server at your home network hosting the websites.\n\nEnable port forwarding for TCP ports 80/443 to `server_ip` (IP from your home network) associated to the server at home network.\n\n| WAN Port | LAN IP | LAN Port |\n|----------|--------|----------|\n| 80 | `server_ip` | 80 |\n| 443 | `server_ip`| 443 |\n\n### Configure OS level Firewall\n\nConfigure local firewall at OS level to enable the incoming traffic on ports 80 and 443\n\nFor example: in case of Ubuntu OS, Ubuntu's embedded firewall (ufw) need to be configured, allowing only incoming SSH, HTTP and HTTPS traffic.\n  ```\n  sudo ufw allow 22\n  sudo ufw allow 80\n  sudo ufw allow 443\n  sudo ufw enable\n  ```\n\nIf the OS is configured with Iptables rules by default (i.e.: Oracle Cloud Ubuntu's VM are created with ufw disabled but wiht Iptables configured), Iptables rules need to be added to enable the incoming traffic\n```\nsudo iptables -I INPUT 6 -m state --state NEW -p tcp --match multiport --dports 80,443 -j ACCEPT\nsudo netfilter-persistent save\n```\n\n## DNS configuration\n\nUsing your DNS provider, add the DNS records of the webservices you want to publish pointing to the public IP address of the server. \n\nIn case of using a Cloud Service Provided, the IP address assigned to the VM created. VM need to be created with a external IP (public IP address).\n\nIn case of hosting at home the IP address assigned by your ISP (public IP address of your home network)\n\nIn case of ISP is using dynmaic IP public addresses, Dynamic DNS must be configured to keep up to date the DNS records mapped to the assigned public IP addresses\n\n\n### Configure Dynamic DNS (Selfhosting at home)\n\nIn case that your ISP only provide you dynamic IP address, IP address associated to DNS records need to be dynamically updated. Most DNS providers supports DynDNS with an open protocol [Domain Connect](https://www.domainconnect.org/) enabling the automatic DNS update ousing the IP public address assigned by the ISP.\nFor example IONOS DNS provider provides the following [instructions](https://www.ionos.com/help/domains/configuring-your-ip-address/connecting-a-domain-to-a-network-with-a-changing-ip-using-dynamic-dns-linux/) to configure DynDNS\n\n- Step 1: Install python package\n\n  ```shell\n  pip3 install domain-connect-dyndns\n  ```\n\n- Step 2: Configure domain to be dynamically updated\n\n  ```shell\n  domain-connect-dyndns setup --domain \u003cyour-domain\u003e\n  ```\n\n- Step 3: Update it\n  \n  ```shell\n  domain-connect-dyndns update --all\n  ```\n\n## Docker configuration\n\n### Installing docker and docker-compose\n\nDocker and docker compose need to be installed on the server.\nAnsible can be used to automatically deploy docker and docker compose on the server\n\n### Create docker networks\n\nCreate a couple of docker network to interconnect all docker containers:\n\n```shell\ndocker network create frontend\ndocker network create backend\n```\n\nContainers accesing to `frontend` network are the only ones that are exposing its ports to the host. Since the host will have internet acces, those exposed services will be accesible from Internet. Traefik container will be the only container to be attached to this network.\n\nContainers accesing to `backend` network are not exposing any port to the server and so they are not accesible directly form internet. All backend containers will be attached to this network.\n\n## Configuring and running Traefik with Docker\n\n\nTraefik discovers automatically the routing configuration to be applied to each backend service, through the annotations specified in each of the backend containers (`labels` section in docker-compose file).\n\n### Securing access to Docker API\n\nFor doing the automatic discovery of services, Traefik requires access to the docker socket to get its dynamic configuration. As Traefik official [documentation](https://doc.traefik.io/traefik/providers/docker/#docker-api-access) states, \"Accessing the Docker API without any restriction is a security concern: If Traefik is attacked, then the attacker might get access to the underlying host\".\n\nThere are several mechanisms to secure the access to Docker API, one of them is the use of a docker proxy like the one provided by Tecnativa, [Tecnativa's Docker Socket Proxy](https://github.com/Tecnativa/docker-socket-proxy). Instead of allowing our publicly-facing Traefik container full access to the Docker socket file, we can instead proxy only the API calls we need with Tecnativa’s Docker Socket Proxy project. This ensures Docker’s socket file is never exposed to the public along with all the headaches doing so could cause an unknowing site owner.\n\nSetting up Docker Socket Proxy. In the home directory create initial `docker-compose.yaml` file\n\n```yml\nversion: \"3.8\"\n\nservices:\n  dockerproxy:\n    container_name: docker-proxy\n    environment:\n      CONTAINERS: 1\n    image: tecnativa/docker-socket-proxy\n    networks:\n      - backend\n    ports:\n      - 2375\n    volumes:\n      - \"/var/run/docker.sock:/var/run/docker.sock:ro\"\n\nnetworks:\n  backend:\n    external: true\n\n```\n\n### Create folders and basic traefik configuration\n\n- Step create traefik directory within User's home directory\n\n   mkdir  ~/traefik\n\n- Create Traefik configuration file `traefik.yml`\n\n  ```yml\n  api:\n    dashboard: true\n    debug: false\n\n  entryPoints:\n    http:\n      address: \":80\"\n    https:\n      address: \":443\"\n\n  providers:\n    docker:\n      endpoint: \"tcp://docker-proxy:2375\"\n      watch: true\n      exposedbydefault: false\n      network: backend\n\n  certificatesResolvers:\n    http:\n      acme:\n        email: admin@ricsanfre.com\n        storage: acme.json\n        httpChallenge:\n          entryPoint: http\n\n  ```\n  This configuration file:\n\n  - Enables Traefik dashoard (`api.dashboard`= true)\n  - Configure Traefik HTTP and HTTPS default ports as entry points (`entryPoints`)\n  - Configure Docker as provider (`providers.docker`). Instead of using docker socket file, it uses as `endpoint` the Socket Proxy. Do not expose the containers by default (`exposedbydefault`), unless specified at container level with a label (`traefik.enable=true`), and use `backend` network as default for communicating with all containers.\n  - Configure Traefik to automatically generate SSL certificates using Let's Encrypt (`certificatesResolvers`). ACME protocol is configured to use http challenge.\n\n- Create empty `acme.json` file used to store SSL certificates generated by Traefik.\n\n    touch acme.json\n    chmod 600 acme.json\n\n- Add Traefik service to docker-compose.yml file\n\n```yml\nservices:\n  traefik:\n    depends_on:\n      - dockerproxy\n    image: traefik\n    container_name: traefik\n    restart: unless-stopped\n    security_opt:\n      - no-new-privileges:true\n    networks:\n      - frontend\n    ports:\n      - 80:80\n      - 443:443\n    volumes:\n      - /etc/localtime:/etc/localtime:ro\n      - ./traefik/traefik.yml:/traefik.yml:ro\n      - ./traefik/acme.json:/acme.json\n```\n\n### Annotating containers with Traefik rules\n\nTraefik discovers automatically the routing configuration to be applied to each backend service, through the annotations specified in each of the backend containers (`labels` section in docker-compose file).\n\nFor example, to configure access to a backend service exposed at `myservice.domain.com` Traefik `router` rules must be specified to redirect the traffik to the proper container. Additionally routing modifiers (`middlewares`) can be used for redirecting HTTP to HTTPS traffic or to apply an authentication method.\n\nFor each backend service exposed through Traefik, a couple of `router` rules can be specified (one for handling HTTP traffic and another for handling HTTPS)\n\n- Router for HTTP incoming traffic. Router rule name: will be the `\u003cservice_name\u003e` where `service_name` is the associated service in the docker-compose file.\n   \n  - `traefik.http.routers.\u003cservice_name\u003e.rule=Host(\u003cservice_domain\u003e)`\n  - `traefik.http.routers.\u003cservice_name\u003e.entrypoint=http`\n  - `traefik.http.routers.\u003cservice_name\u003e.middlewares=\u003cservice_name\u003e-https-redirect`\n  - `traefik.http.middlewares.\u003cservice_name\u003e-https-redirect.redirectscheme.scheme=https`\n\n  Where \u003cservice_domain\u003e specifies that the incoming traffic to that domain must be redirected to the container.\n\n  And the configured `middleware` redirect all HTTP incoming traffic to the HTTPS entry point, and so to the HTTPS router rule.\n   \n- Router for HTTPS incoming traffic. Router rule name: will be `\u003cservice_name\u003e-secure` \n  \n  - `traefik.http.routers.\u003cservice_name\u003e-secure.rule=Host(\u003cservice_domain\u003e)`\n  - `traefik.http.routers.\u003cservice_name\u003e-secure.entrypoint=https`\n  - `traefik.http.routers.\u003cservice_name\u003e-secure.tls=true`: Enabling TLS certificates generation\n  - `traefik.http.routers.\u003cservice_name\u003e-secure\u003e.tls.certresolver=http`: Issue the SSL certificate with the resolver specified in Traefik configuration (`traefik.yml`): Let's Encrypt (ACME protocol) with HTTP challenge.\n\n- Additionally we need to tell Traefik which port of the container is being used.\n\n  - `traefik.http.services.\u003cservice_name\u003e.loadbalancer.server.port=\u003cbackend_port\u003e`. Use container port \u003cbackend_port\u003e to redirect all the traffic.\n  \n```yml\n...\nmy_service:\n  labels:\n    # Explicitly tell Traefik to expose this container\n    - \"traefik.enable=true\"\n    # The domain the service will respond to\n    - \"traefik.http.routers.whoami.rule=Host(`whoami.domain.com`)\"\n    # Allow request only from the predefined entry point named \"http\"\n    - \"traefik.http.routers.whoami.entrypoints=http\"\n    # Redirect all incoming http traffic to HTTPS\n    - \"traefik.http.routers.whoami.middlewares=whoami-https-redirect\"\n    - \"traefik.http.middlewares.whoami-https-redirect.redirectscheme.scheme=https\"\n    # Domain used for secure routing configuration\n    - \"traefik.http.routers.whoami-secure.rule=Host(`whoami.domain.com`)\"\n    # Allow requests in the predefined entry point \"https\"\n    - `traefik.http.routers.whoami-secure.entrypoint=https`\n    # Enabling TLS certificates generation\n    - \"traefik.http.routers.whoami-secure.tls=true\"\n    # Use SSL certificate resolver specified in configuration (Lets Encrypt)\n    - \"traefik.http.routers.whoami-secure.tls.certresolver=http`\n```\n\n### Configuring basic authentication access to Traefik dashboard\n\nTraefik dashboard will be enabled. By default it does not provide any authentication mechanisms. Traefik HTTP basic authentication mechanims will be used.\n\nIn case that the backend does not provide authentication/authorization functionality, Traefik can be configured to provide HTTP authentication mechanism (basic authentication, digest and forward authentication).\n\nTraefik's [Basic Auth Middleware](https://doc.traefik.io/traefik/middlewares/http/basicauth/) for providing basic auth HTTP authentication.\n\nUser:hashed-passwords pairs needed by the middleware can be generated with `htpasswd` utility. The command to execute is:\n\n```shell\nhtpasswd -nb \u003cuser\u003e \u003cpasswd\u003e\n```\n\n`htpasswd` utility is part of `apache2-utils` package. In order to execute the command it can be installed with the command: `sudo apt install apache2-utils`\n\nAs an alternative, docker image can be used and the command to generate the user:hashed-password pairs is:\n      \n```shell\ndocker run --rm -it --entrypoint /usr/local/apache2/bin/htpasswd httpd:alpine -nb user password\n```\nFor example:\n\n```shell\nhtpasswd -nb admin secretpassword\nadmin:$apr1$3bVLXoBF$7rHNxHT2cLZLOr57lHBOv1\n```\n\n### Add Traefik service to docker-compose.yml file\n\n```yml\nservices:\n  traefik:\n    depends_on:\n      - dockerproxy\n    image: traefik\n    container_name: traefik\n    restart: unless-stopped\n    security_opt:\n      - no-new-privileges:true\n    networks:\n      - frontend\n    ports:\n      - 80:80\n      - 443:443\n    volumes:\n      - /etc/localtime:/etc/localtime:ro\n      - ./traefik/traefik.yml:/traefik.yml:ro\n      - ./traefik/acme.json:/acme.json\n    labels:\n      - \"traefik.enable=true\"\n      - \"traefik.http.routers.traefik.entrypoints=http\"\n      - \"traefik.http.routers.traefik.rule=Host(`monitor.yourdomain.com`)\"\n      - \"traefik.http.middlewares.traefik-auth.basicauth.users=admin:$$apr1$$3bVLXoBF$$7rHNxHT2cLZLOr57lHBOv1\"\n      - \"traefik.http.middlewares.traefik-https-redirect.redirectscheme.scheme=https\"\n      - \"traefik.http.routers.traefik.middlewares=traefik-https-redirect\"\n      - \"traefik.http.routers.traefik-secure.entrypoints=https\"\n      - \"traefik.http.routers.traefik-secure.rule=Host(`monitor.yourdomain.com`)\"\n      - \"traefik.http.routers.traefik-secure.middlewares=traefik-auth\"\n      - \"traefik.http.routers.traefik-secure.tls=true\"\n      - \"traefik.http.routers.traefik-secure.tls.certresolver=http\"\n      - \"traefik.http.routers.traefik-secure.service=api@internal\"\n```\n\nWhere:\n  - Replace `monitor.yourdomain.com` in `traefik.http.routers.traefik.rule` and `traefik.http.routers.traefik-secure.rule` labels by your domain\n  - Replace htpasswd pair generated before in `traefik.http.middlewares.traefik-auth.basicauth.users` label. \n    \u003e NOTE: If te resulting string has any `$` you will need to modify them to be `$$` - this is because docker-compose uses `$` to signify a variable. By adding `$$` we still docker-compose that it’s actually a `$` in the string and not a variable.) \n\nThis configuration will start Traefik service and enabling its dashboard at `monitor.yourdomain.com`. Enabling HTTPS, generating a TLS and  redirecting all HTTP traffic to HTTPS.\n\n## Configuring and running web analytics service (Matomo) behind Traefik\n\nMatomo service is composed of two containers: \n1) SQL database (MariaDB) \n2) Apache-based PHP website\n\n- Step 1: Create matomo directories within User's home directory\n\n    mkdir  ~/matomo\n    mkdir -p ~/matomo/db\n    mkdir -p ~/matomo/www-data\n\n  `matomo/db` is a host directory to be used as docker bind mount for storing MariaDB's data\n  `matomo/www-data` is a host directory to be used as docker bind mount for storing Matomo's website\n\n- Step 2: Create environment file\n \n  This file will contain environment variables for the two containers\n\n  `~/matomo/db.env`\n  ```\n  MYSQL_ROOT_PASSWORD=\u003cmysql_root_user_password\u003e\n  MYSQL_DATABASE=matomo\n  MYSQL_USER=matomo\n  MYSQL_PASSWORD=\u003cmatomo_user_password\u003e\n  MATOMO_DATABASE_ADAPTER=mysql\n  MATOMO_DATABASE_TABLES_PREFIX=matomo_\n  MATOMO_DATABASE_USERNAME=matomo\n  MATOMO_DATABASE_PASSWORD=\u003cmatomo_user_password\u003e\n  MATOMO_DATABASE_DBNAME=matomo\n  ```\n  This environment files contains MariaDB root user credentials `MYSQL_ROOT_PASSWORD` and the database name (`matomo`) and the user (`matomo`) credentials to be used by Matomo.\n\n- Step 3: Add MariaDB service to docker-compose.yml file\n\n  ```yml\n  db:\n    image: mariadb\n    container_name: mariadb\n    networks:\n      - backend\n    command: --max-allowed-packet=64MB\n    restart: always\n    volumes:\n      - ./matomo/db:/var/lib/mysql\n    env_file:\n      - ./matomo/db.env\n  ```\n  \u003e NOTE: MariaDB container connected only to `backend` docker network. Host's matomo/db directory is mounted as MariaDB data base direcoty `/var/lib/mysql` \n\n- Step 4: Add annotated Matomo container to docker-compose.yml file\n\n  ```yml\n  matomo:\n    depends_on:\n      - db\n    image: matomo\n    container_name: matomo\n    restart: always\n    networks:\n      - backend\n    volumes:\n      - ./matomo/www-data:/var/www/html\n    environment:\n      - MATOMO_DATABASE_HOST=db\n    env_file:\n      - ./matomo/db.env\n    ports:\n      - target: 80\n        protocol: tcp\n    labels:\n      - \"traefik.enable=true\"\n      - \"traefik.http.routers.matomo.entrypoints=http\"\n      - \"traefik.http.routers.matomo.rule=Host(`matomo.yourdomain.com`)\"\n      - \"traefik.http.middlewares.matomo-https-redirect.redirectscheme.scheme=https\"\n      - \"traefik.http.routers.matomo.middlewares=matomo-https-redirect\"\n      - \"traefik.http.routers.matomo-secure.entrypoints=https\"\n      - \"traefik.http.routers.matomo-secure.rule=Host(`matomo.yourdoamin.com`)\"\n      - \"traefik.http.routers.matomo-secure.tls=true\"\n      - \"traefik.http.routers.matomo-secure.tls.certresolver=http\"\n      - \"traefik.http.routers.matomo-secure.service=matomo\"\n      - \"traefik.http.services.matomo.loadbalancer.server.port=80\"\n  ```\n  \u003e NOTE: matomo container connected only to `backend` docker network. Host's matomo/www directory is mounted as Apaches's website directory `/var/www/html.\n  \u003e\n  \u003e Container annotated to be discovered by Traefik, exposing container tcp port 80, and creating the Traefik's rules to route the incoming traffic to Matomo's URL (`matomo.yourdomain.com`)\n\n- Step 5: Finishing Matomo installation\n\n  In order to finalize Matomo installation, Apache web server running on `matomo.yourdomain.com` need to be accesed and the procedure described in the [official documentation](https://matomo.org/docs/installation/) must be followed.\n\n  For doing so you need to run the containers with the commad:\n\n  ```shell\n  docker-compose up -d\n  ```\n\n## Configuring and running comments platform (remark42) behind Traefik\n\n- Step 1: Create remark42 directories within User's home directory\n\n    mkdir  ~/remark42\n    mkdir -p ~/remark42/var\n\n  `remartk/var` is a host directory to be used as docker bind mount for storing remark42's data\n  \n- Step 2: Create environment file\n \n  This file will contain environment variables for remark42 container\n\n  `~/remark42/remark42.env`\n\n  ```\n  REMARK_URL=http://remark42.yourdoamin.com\n  SECRET=\u003cremark42_secret\u003e\n  STORE_BOLT_PATH=/srv/var/db\n  BACKUP_PATH=/srv/var/backup\n  SITE=\u003csite_id\u003e\n  AUTH_ANON=true\n  ```\n\n  Where:\n  - `site_id`: identifies the list of sites (`,` separated) which remark42 is storing the comments for.\n    \n    It must be the same `site_id` in the java script code added to your website. See [remark42 installation documentation](https://remark42.com/docs/getting-started/installation/)\n\n  \u003e NOTE: In this case only anonymous comments are being enabled. Other environment variables enables non-anonymous comments and integration of the authorization with external platforms Github, Google, etc.\n\n- Step 3: Add annotated remark42 container to docker-compose.yml file\n\n  ```yml\n  ## Remark42\n  remark42:\n    image: umputun/remark42:latest\n    container_name: \"remark42\"\n    hostname: \"remark42\"\n    restart: always\n    networks:\n      - backend\n    volumes:\n      - ./remark42/var:/srv/var\n    ports:\n      - target: 80\n        protocol: tcp\n    env_file:\n      - ./remark42/remark42.env\n    environment:\n      - APP_UID=1000  # runs Remark42 app with non-default UID\n      - TIME_ZONE=Europe/Madrid\n    labels:\n      - \"traefik.enable=true\"\n      - \"traefik.http.routers.remark42.entrypoints=http\"\n      - \"traefik.http.routers.remark42.rule=Host(`remark42.yourdoamin.com`)\"\n      - \"traefik.http.middlewares.remark42-https-redirect.redirectscheme.scheme=https\"\n      - \"traefik.http.routers.remark42.middlewares=remark42-https-redirect\"\n      - \"traefik.http.routers.remark42-secure.entrypoints=https\"\n      - \"traefik.http.routers.remark42-secure.rule=Host(`remark42.yourdoamin.com`)\"\n      - \"traefik.http.routers.remark42-secure.tls=true\"\n      - \"traefik.http.routers.remark42-secure.tls.certresolver=http\"\n      - \"traefik.http.routers.remark42-secure.service=remark42\"\n      - \"traefik.http.services.remark42.loadbalancer.server.port=80\"\n      - \"traefik.http.middlewares.remark42.headers.accesscontrolalloworiginlist=*\"\n  ```\n\n  \u003e NOTE: remark42 container connected only to `backend` docker network. Host's remark42/var directory is mounted as remark42's var directory  `/srv/var`.\n  \u003e\n  \u003e Container annotated to be discovered by Traefik, exposing container tcp port 80, and creating the Traefik's rules to route the incoming traffic to Remark42's URL (`remark42.yourdomain.com`).\n  \u003e\n  \u003e [Traefik middleware cors headers](https://doc.traefik.io/traefik/middlewares/http/headers/#cors-headers) must be used to avoid CORS issues with remark42.\n  \u003e\n  \u003e  `traefik.http.middlewares.remark42.headers.accesscontrolalloworiginlist=*` to allow request from all orginins.\n\n## Configuring and running your static website behind Traefik using Matomo and Remark42 services\n\nJekyll can be used for creating your static website. HTML templates need to be modified to include remark42 and matomo javascript code and remark42's html code.\n\n### Creating your website with Jekyll\n\nAs a quick example:\n\n- Step 1: Install jekyll (as prerequisite ruby package need to be installed)\n\n  ```shell\n  gem install bundler jekyll\n  ```\n- Step 2: Create a new jekyll site using default theme (`minima`)\n\n  In $HOME directory execute\n  ```shell\n  jekyll new mywebsite\n  ```\n\n- Step 3: Create html code snippets containing matamo and remark java sctipt code\n\n  This code snippets will be included in the HTML header of all the pages.\n\n  Include matomo code snippet. This code from Matomo UI whenever a new site is added to be tracked.\n\n  `_includes/matomo-analytics.html`\n  ```html\n  \u003c!-- Matomo --\u003e\n  \u003cscript\u003e\n    var _paq = window._paq = window._paq || [];\n    /* tracker methods like \"setCustomDimension\" should be called before \"trackPageView\" */\n    _paq.push(['trackPageView']);\n    _paq.push(['enableLinkTracking']);\n    (function() {\n      var u=\"//matomo.yourdomain.com/\";\n      _paq.push(['setTrackerUrl', u+'matomo.php']);\n      _paq.push(['setSiteId', 'mywebsite']);\n      var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];\n      g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);\n    })();\n  \u003c/script\u003e\n  \u003c!-- End Matomo Code --\u003e\n  ```\n  \u003e NOTE: Here it is important to have the right URL for the matomo service  `matomo.yourdomain.com`  and the `site_id` identifying your website.\n\n  Include remark42 code snippet. Code comes from [remark42 documentation](https://remark42.com/docs/configuration/frontend/) \n\n  `_includes/remark42.html`\n  ```html\n  \u003c!-- Remark42 --\u003e\n  \u003cscript\u003e\n    var remark_config = {\n      host: 'remark42.yourdomain.com',\n      site_id: 'mywebsite',\n      components: ['embed'], \n      theme: 'dark',\n    };\n  \u003c/script\u003e\n  \u003cscript\u003e!function(e,n){for(var o=0;o\u003ce.length;o++){var r=n.createElement(\"script\"),c=\".js\",d=n.head||n.body;\"noModule\"in r?(r.type=\"module\",c=\".mjs\"):r.async=!0,r.defer=!0,r.src=remark_config.host+\"/web/\"+e[o]+c,d.appendChild(r)}}(remark_config.components||[\"embed\"],document);\u003c/script\u003e\n  \u003c!-- End Remark42 Code --\u003e\n  ```\n  \n  \u003e NOTE: Here it is important to set javascript variable `remark_config` containing the `host` where remark42 service is running (`remark42.yourdomain.com`) and the `site_id` identifying your website.\n\n- Step 3: Modify header html code snippets to include remark42 and matomo javascript\n  \n  `_includes/head.html`\n  ```html\n  \u003chead\u003e\n    \u003cmeta charset=\"utf-8\"\u003e\n    \u003cmeta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\"\u003e\n    \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\"\u003e\n    \u003clink rel=\"stylesheet\" href=\"{{ \"/assets/main.css\" | relative_url }}\"\u003e\n   \n    {% if jekyll.environment == 'production' and site.matomo_analytics %}\n      {% include matomo-analytics.html -%}\n    {% endif %}\n    {% if page.comments and jekyll.environment == 'production'%}\n       {% include remark42.html %}\n    {% endif %}\n \n  \u003c/head\u003e\n  ```\n\n- Step 4: Modify posts html layout to include remark42 comments\n\n  `_layouts/post.html`\n\n  ```html\n  ....\n  {% if page.comments and jekyll.environment == 'production' %}\n  \u003cdiv id=\"remark42\"\u003e\u003c/div\u003e\n  {% endif %}\n  ```\n\n  \u003e NOTE: matomo analytics ad remark42 snippet are only included in case the site is generated for production environment running the command\n  \u003e \n  \u003e ```shell\n  \u003e JEKYLL_ENV=production bundle exec jekyll serve\n  \u003e ```\n  \u003e\n  \u003e Matomo analytics is only include if `matomo_analytics` is set to true in Jekylls' `_config.yml` file.\n  \u003e Remark42's comments are only enabled for those posts having the variable `comments` set to true \n\n- Step 5: Generate site HTML code executing the command\n\n  ```shell\n  JEKYLL_ENV=production bundle exec jekyll build\n  ```\n\n  HTML generated code is under `_site` directory\n\n### How to deploy the static site in Docker\nA simple Apache docker image (`httpd`) can be used and the complete static site generated by Jekyll (`_site` directory) can mounted in the docker container as bind mount of `/usr/local/apache2/htdocs`\n\n- Step 1: Create mywebsite directories within User's home directory\n\n    mkdir  ~/mywebsite\n    mkdir -p ~/mywebsite/_site\n \n- Step 2: Copy the Jekyll generated code of your website to `~/mywebsite/_site`\n\n- Step 3: Add apache container server to `docker-compose.yml` file\n\n  ```yml\n  mywebsite:\n    depends_on:\n      - traefik\n    image: httpd:2.4-alpine\n    container_name: \"mywebsite\"\n    hostname: \"mywebsite\"\n    restart: always\n    networks:\n      - backend\n    volumes:\n      - ./mywebsite/_site:/usr/local/apache2/htdocs/\n    ports:\n      - target: 80\n        protocol: tcp\n    labels:\n      - \"traefik.enable=true\"\n      - \"traefik.http.routers.mywebsite.entrypoints=http\"\n      - \"traefik.http.routers.mywebsite.rule=Host(`$MYWEBSITE_URL`)\"\n      - \"traefik.http.middlewares.mywebsite-https-redirect.redirectscheme.scheme=https\"\n      - \"traefik.http.routers.mywebsite.middlewares=mywebsite-https-redirect\"\n      - \"traefik.http.routers.mywebsite-secure.entrypoints=https\"\n      - \"traefik.http.routers.mywebsite-secure.rule=Host(`$MYWEBSITE_URL`)\"\n      - \"traefik.http.routers.mywebsite-secure.tls=true\"\n      - \"traefik.http.routers.mywebsite-secure.tls.certresolver=http\"\n      - \"traefik.http.routers.mywebsite-secure.service=mywebsite\"\n      - \"traefik.http.services.mywebsite.loadbalancer.server.port=80\"\n  ```\n\n  \u003e NOTE: mywebsite container is running a basic apache image. Host's mywebsite/_site directory is mounted as Apaches's default html docs directory `/usr/local/apache2/htdocs/.\n  \u003e \n  \u003e Container is annotated so it can be routed by Traefik.\n\n\n## Backup\n\n### Remark42 \n\nRemark42 by default makes daily backup files in `~/remark42/var/backup`\n\nThis directory must be backed up daily\n\n### Matomo\n\n- Matomo website\n  Backup `~/matomo/www-data` directory\n- Matomo's MySQL database\n  To perform Matomo's MySQl database backup use the provided script `matomo_mysql_backup.sh`\n\n  This script exexutes a mysql dump command storing the result in compressed format in `~/matomo/backup/`\n  This script must be executed daily and backup directory backed up daily.\n\n### Backup documents references\n\n- [Remark42 automatic and manual backup](https://remark42.com/docs/backup/backup/)\n- [Matomo backup best practices](https://matomo.org/faq/on-premise/what-are-the-requirements-and-recommendations-for-matomo-backup/)\n- [Matomo MySQL backup how to](https://matomo.org/faq/how-to/how-do-i-backup-and-restore-the-matomo-data/)\n\n## Docker-compose commands to create/start/stop/upgrade the containers\n\nAll commands need to be executed in $HOME directory, where docker-compose.yml file is located\n\n### Creating containers and starting the services\n\n```shell\ndocker-compose up -d\n```\n\n### Stopping the containers\n\nTo stop all the services\n```shell\ndocker-compose stop\n```\n\nTo stop just one of the services\n\n```shell\ndocker-compose stop \u003cservice_name\u003e\n```\n\n### Starting the containers\n\nTo start all the services\n```shell\ndocker-compose start\n```\n\nTo start just one of the services\n\n```shell\ndocker-compose start \u003cservice_name\u003e\n```\n\n### Deleting the containers\n\n```shell\ndocker-compose down\n```\n\u003e NOTE: Since all data is stored in local host (using docker bind mounts), this command will not loose any important data.\n\n### Check logs of one container\n\n```shell\ndocker-compose logs -f \u003cdocker_service_name\u003e\n```\n\n### Updating your docker images ##\n\nThis procedure indicates how to upgrade docker images of any of the services (matomo, remark42, etc.)\n\nUpdating with Docker Compose\n\n1. Pull the new image from Docker Hub:\n\n    ```shell\n    docker-compose pull \u003cdocker_service_name\u003e\n    ```\n\n1. Recreate the running container:\n\n    ```shell\n    docker-compose up --detach \u003cdocker_service_name\u003e\n    ```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fricsanfre%2Fpublic-websites-docker","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fricsanfre%2Fpublic-websites-docker","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fricsanfre%2Fpublic-websites-docker/lists"}