Featured image of post Setup a QMK Configurator environment with docker compose (Part 2)

Setup a QMK Configurator environment with docker compose (Part 2)

Adding a reverse proxy and SSL to a self-hosted QMK Configurator dev site with nginx proxy and letsencrypt nginx proxy companion.

Intro

Part 1 of this guide explained how to get your own QMK Configurator site up and running online. This second guide will cover some of the nice-to-haves: custom domain name, reverse proxy, SSL. It sounds like a lot of work but it’s actually really simple and can be done almost completely in one docker-compose file.

Reverse Proxy

A reverse proxy here basically routes requests made to your single server to the appropriate exposed port based on the domain. For instance:

config.<your_site>  -->  <your_site>:5000
api.<your_site>     -->  <your_site>:5001
minio.<your_site>   -->  <your_site>:9000

DNS Settings

For this guide you’ll need to have a registered domain name. It can be anything, and you can still use it for whatever you are today. You’ll just be creating sub-domains off of it.

You’ll need to create three A Record entries in the settings on your domain registrar’s website: config, api, minio. The value that you specify is the server’s public IP address. Here’s how this would look using Namecheap

DNS Settings

nginx-proxy

The simplest reverse proxy solution I found was a package called nginx-proxy. It’s basically as simple as adding a new section to your docker-compose.yml file. I’ve higlighted the new/changed portions below:

docker-compose.yml

  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
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
version: '3'

services:
  qmk_api:
    build: ./qmk_api
    ports:
      - "5001:5001"
    environment:
      PYTHONUNBUFFERED: 1
      API_URL: http://<your_url>:5001/
      QMK_GIT_URL: https://github.com/<your_github>/qmk_firmware.git
      QMK_GIT_BRANCH: master
      FLASK_GRAPHITE_HOST: qmk_metrics_aggregator
      REDIS_HOST: redis
      RELOAD: 1
      S3_HOST: http://<your_url>:9000
      S3_ACCESS_KEY: minio_dev
      S3_SECRET_KEY: minio_dev_secret
      UPDATE_API: 'true'
      VIRTUAL_HOST: api.<your_url>
      VIRTUAL_PORT: 5001
    volumes:
    - ./qmk_api:/qmk_api
    - ./qmk_compiler:/qmk_compiler

  qmk_api_tasks:
    build: ./qmk_api_tasks
    command: /bin/true
    ports:
      - "5002:5000"
    environment:
      PYTHONUNBUFFERED: 1
      API_URL: http://<your_url>:5001/
      #DISCORD_WEBHOOK_URL: <Your private webhook URL>
      MSG_ON_GOOD_COMPILE: "no"
      MSG_ON_BAD_COMPILE: "no"
      MSG_ON_S3_SUCCESS: "no"
      REDIS_HOST: redis
      RELOAD: 1
      S3_HOST: http://<your_url>:9000
      S3_ACCESS_KEY: minio_dev
      S3_SECRET_KEY: minio_dev_secret
      UPDATE_API: 'true'
    volumes:
      - ./qmk_api_tasks:/qmk_api_tasks
      - ./qmk_compiler:/qmk_compiler

  qmk_bot:
    build: ./qmk_bot
    environment:
      #DISCORD_WEBHOOK_URL: <Your private webhook URL>
      PYTHONUNBUFFERED: 1
      API_URL: http://<your_url>:5001/
      REDIS_HOST: redis
      RELOAD: 1
      S3_HOST: http://<your_url>:9000
    volumes:
    - ./qmk_bot:/qmk_bot
    - ./qmk_compiler:/qmk_compiler

  qmk_compiler:
    build: ./qmk_compiler
    environment:
      GRAPHITE_HOST: qmk_metrics_aggregator
      MINIO_ACCESS_KEY: minio_dev
      MINIO_SECRET_KEY: minio_dev_secret
      PYTHONUNBUFFERED: 1
      API_URL: http://<your_url>:5001/
      LOG_LEVEL: DEBUG
      QMK_GIT_URL: https://github.com/<your_github>/qmk_firmware.git
      QMK_GIT_BRANCH: master
      REDIS_HOST: redis
      RELOAD: 1
      S3_HOST: http://<your_url>:9000
    volumes:
    - ./qmk_compiler:/qmk_compiler

  qmk_metrics_aggregator:
    build: ./qmk_metrics_aggregator
    environment:
      PYTHONUNBUFFERED: 1
      METRIC_TTL: 600
      GRAPHITE_HOST: graphite
      GRAPHITE_RESOLUTION: 60
    volumes:
    - ./qmk_metrics_aggregator/qmk_metrics_aggregator:/qmk_metrics_aggregator

  qmk_configurator:
    build: ./qmk_configurator
    ports:
      - "5000:80"
    environment:
      QMK_API_URL: http://<your_url>:5001/
      VIRTUAL_HOST: config.<your_url>
      VIRTUAL_PORT: 80
    # To use this you need to have built it locally, IE qmk_configurator/dist needs to exist.                                                                                                                 >
    volumes:
      - ./qmk_configurator/public/keymaps:/qmk_configurator/dist/keymaps

  minio:
    image: minio/minio
    volumes:
      - minio_data:/data
    ports:
      - "9000:9000"
      - "9001:9001"
    environment:
      MINIO_ROOT_USER: minio_dev
      MINIO_ROOT_PASSWORD: minio_dev_secret
      PYTHONUNBUFFERED: 1
      S3_ACCESS_KEY: minio_dev
      S3_SECRET_KEY: minio_dev_secret
      VIRTUAL_HOST: minio.<your_url>
      VIRTUAL_PORT: 9000
    command: server /data --console-address :9001
    restart: always

  redis:
    image: ghcr.io/qmk/qmk_api_redis
    ports:
      - "6379:6379"
    restart: always

  graphite:
    image: ghcr.io/qmk/qmk_graphite
    ports:
      - "5003:80"
    restart: always

  nginx-proxy:
    image: "jwilder/nginx-proxy"
    container_name: "nginx-proxy"
    ports:
      - 80:80
      - 443:443
    environment:
      RESOLVERS: 8.8.8.8
    restart: always
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - nginx-certs:/etc/nginx/certs:rw
      - nginx-vhost:/etc/nginx/vhost.d:rw
      - nginx-html:/usr/share/nginx/html:rw
      - ./my_proxy.conf:/etc/nginx/conf.d/my_proxy.conf:ro
    links:
      - "qmk_api"
      - "qmk_configurator"
      - "minio"
    depends_on:
      - "qmk_api"
      - "qmk_configurator"
      - "minio"

volumes:
  minio_data:
  nginx-certs:
  nginx-html:
  nginx-vhost:
  

Max upload size config

There’s one configuration for nginx-proxy that can’t be set in the docker-compose file. Create one file in the same folder as your docker-compose.yml and name it “my_proxy.conf” with the contents below:

client_max_body_size 100m;

This will create a configuration file for nginx-proxy to increase the maximum file size that can be uploaded. The limit comes into play when the application is uploading the source code zip file to the Minio/S3 bucket.

Now if you run docker compose up, you should be able to access your application in the browser over port 80. Make sure you specify “http” and not “https”, since we do not have SSL enabled yet.

http://<your_url>

SSL

Finally, you can also add SSL to the site so that it can be accessed over HTTPS.

letsencrypt nginx proxy companion

There’s a very simple and handy tool that works with the first one called letsencrypt nginx proxy companion which automates the request/renewal of letsencrypt SSL certificates.

docker-compose.yml

To configure this tool, make the following changes to your docker-compose.yml file.

Note: Due to rate limiting for letsencrypt certificates, you will want to test this before running it to request the actual certificates. You can run in a test mode with dummy certificates by including the following line in your environment variables section: ACME_CA_URI: https://acme-staging-v02.api.letsencrypt.org/directory

  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
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
version: '3'

services:
  qmk_api:
    build: ./qmk_api
    ports:
      - "5001:5001"
    environment:
      PYTHONUNBUFFERED: 1
      API_URL: http://<your_url>:5001/
      QMK_GIT_URL: https://github.com/<your_github>/qmk_firmware.git
      QMK_GIT_BRANCH: master
      FLASK_GRAPHITE_HOST: qmk_metrics_aggregator
      REDIS_HOST: redis
      RELOAD: 1
      S3_HOST: http://<your_url>:9000
      S3_ACCESS_KEY: minio_dev
      S3_SECRET_KEY: minio_dev_secret
      UPDATE_API: 'true'
      VIRTUAL_HOST: api.<your_url>
      VIRTUAL_PORT: 5001
      LETSENCRYPT_HOST: api.<your_url>
      LETSENCRYPT_EMAIL: <your_email>
    volumes:
    - ./qmk_api:/qmk_api
    - ./qmk_compiler:/qmk_compiler

  qmk_api_tasks:
    build: ./qmk_api_tasks
    command: /bin/true
    ports:
      - "5002:5000"
    environment:
      PYTHONUNBUFFERED: 1
      API_URL: http://<your_url>:5001/
      #DISCORD_WEBHOOK_URL: <Your private webhook URL>
      MSG_ON_GOOD_COMPILE: "no"
      MSG_ON_BAD_COMPILE: "no"
      MSG_ON_S3_SUCCESS: "no"
      REDIS_HOST: redis
      RELOAD: 1
      S3_HOST: http://<your_url>:9000
      S3_ACCESS_KEY: minio_dev
      S3_SECRET_KEY: minio_dev_secret
      UPDATE_API: 'true'
    volumes:
      - ./qmk_api_tasks:/qmk_api_tasks
      - ./qmk_compiler:/qmk_compiler

  qmk_bot:
    build: ./qmk_bot
    environment:
      #DISCORD_WEBHOOK_URL: <Your private webhook URL>
      PYTHONUNBUFFERED: 1
      API_URL: http://<your_url>:5001/
      REDIS_HOST: redis
      RELOAD: 1
      S3_HOST: http://<your_url>:9000
    volumes:
    - ./qmk_bot:/qmk_bot
    - ./qmk_compiler:/qmk_compiler

  qmk_compiler:
    build: ./qmk_compiler
    environment:
      GRAPHITE_HOST: qmk_metrics_aggregator
      MINIO_ACCESS_KEY: minio_dev
      MINIO_SECRET_KEY: minio_dev_secret
      PYTHONUNBUFFERED: 1
      API_URL: http://<your_url>:5001/
      LOG_LEVEL: DEBUG
      QMK_GIT_URL: https://github.com/<your_github>/qmk_firmware.git
      QMK_GIT_BRANCH: master
      REDIS_HOST: redis
      RELOAD: 1
      S3_HOST: http://<your_url>:9000
    volumes:
    - ./qmk_compiler:/qmk_compiler

  qmk_metrics_aggregator:
    build: ./qmk_metrics_aggregator
    environment:
      PYTHONUNBUFFERED: 1
      METRIC_TTL: 600
      GRAPHITE_HOST: graphite
      GRAPHITE_RESOLUTION: 60
    volumes:
    - ./qmk_metrics_aggregator/qmk_metrics_aggregator:/qmk_metrics_aggregator

  qmk_configurator:
    build: ./qmk_configurator
    ports:
      - "5000:80"
    environment:
      QMK_API_URL: http://<your_url>:5001/
      VIRTUAL_HOST: config.<your_url>
      VIRTUAL_PORT: 80
      LETSENCRYPT_HOST: config.<your_url>
      LETSENCRYPT_EMAIL: <your_email>
    # To use this you need to have built it locally, IE qmk_configurator/dist needs to exist.                                                                                                                 >
    volumes:
      - ./qmk_configurator/public/keymaps:/qmk_configurator/dist/keymaps

  minio:
    image: minio/minio
    volumes:
      - minio_data:/data
    ports:
      - "9000:9000"
      - "9001:9001"
    environment:
      MINIO_ROOT_USER: minio_dev
      MINIO_ROOT_PASSWORD: minio_dev_secret
      PYTHONUNBUFFERED: 1
      S3_ACCESS_KEY: minio_dev
      S3_SECRET_KEY: minio_dev_secret
      VIRTUAL_HOST: minio.<your_url>
      VIRTUAL_PORT: 9000
      LETSENCRYPT_HOST: minio.<your_url>
      LETSENCRYPT_EMAIL: <your_email>
    command: server /data --console-address :9001
    restart: always

  redis:
    image: ghcr.io/qmk/qmk_api_redis
    ports:
      - "6379:6379"
    restart: always

  graphite:
    image: ghcr.io/qmk/qmk_graphite
    ports:
      - "5003:80"
    restart: always

  nginx-proxy:
    image: "jwilder/nginx-proxy"
    container_name: "nginx-proxy"
    ports:
      - 80:80
      - 443:443
    environment:
      RESOLVERS: 8.8.8.8
    restart: always
    labels:
      com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy: "true"
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - nginx-certs:/etc/nginx/certs:rw
      - nginx-vhost:/etc/nginx/vhost.d:rw
      - nginx-html:/usr/share/nginx/html:rw
      - ./my_proxy.conf:/etc/nginx/conf.d/my_proxy.conf:ro
    links:
      - "qmk_api"
      - "qmk_configurator"
      - "minio"
    depends_on:
      - "qmk_api"
      - "qmk_configurator"
      - "minio"

  letsencrypt:
    image: "jrcs/letsencrypt-nginx-proxy-companion"
    container_name: "nginx-letsencrypt"
    restart: always
    environment:
      #ACME_CA_URI: https://acme-staging-v02.api.letsencrypt.org/directory
      NGINX_PROXY_CONTAINER: nginx-proxy
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - nginx-certs:/etc/nginx/certs
      - nginx-vhost:/etc/nginx/vhost.d
      - nginx-html:/usr/share/nginx/html
    links:
      - "nginx-proxy"
    depends_on:
      - "nginx-proxy"

volumes:
  minio_data:
  nginx-certs:
  nginx-html:
  nginx-vhost:
  

Wrap Up

QMK Configurator

If all goes well, your containers should spin up without any errors and all be able to talk to eachother.

You can try out my configurator dev site here: https://config.snacksthecat.com/

Built with Hugo
Theme Stack designed by Jimmy