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

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

Using the qmk_web_stack project to host your own Configurator sandbox

Last updated 2023-06-19

Intro

This guide explains the steps I went through to setup my own instance of QMK Configurator. These steps should serve as a general how-to guide and hopefully fill in some gaps for people who may have gotten stuck. Some of the details, specifically adding your own custom keyboard to the configurator, can be approached a few different ways; so the information in that section may be less prescriptive and more for reference.

I did this on a machine running Ubuntu Jammy. I was not able to get this all working locally due to some networking challenges I couldn’t figure out. Due to that issue (and since I wanted to give some people access over the Internet anyways), I hosted my configurator using a cloud service provider. I chose to use Kamatera since they offer a 30 day free trial for 1 server and pricing beyond that was reasonable, but anything similar would work.

As a prerequisite you’ll need to already have Docker and Docker Compose setup. Instructions for installing Docker on Ubuntu (and other distros) can be found on the Docker website. You don’t need Docker Desktop unless you find that useful for managing your containers.

I’d recommend reading through this full post first before trying to follow it step-by-step. I don’t explicity state it in any of the steps but you should try building the containers at various points along the way. The command is:

docker compose build

Setup

Clone QMK Web Stack Repo

Clone the qmk_web_stack repo from GitHub.

git clone --recurse-submodules https://github.com/qmk/qmk_web_stack.git

Run this script in the main project directory to update all submodules to their latest.

./fix-submodules.sh

Configuring the Web Stack

docker-compose.yml

Open docker-compose.yml in a text editor and update the configuration values.

Below is an example where I’ve highlighted the changes for my config.

Note: I couldn’t get the configurator working end-to-end on a local machine, so I ended up using a cloud hosting service. As such, this config assumes that you’re doing the same. You might still be able to get it to work locally.

Double Note: You should probably change to minio secret if you plan to host this online, but you may want to try to get everything up and running with the default secret first.

  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
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'
    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/
    # 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
    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

volumes:
  minio_data:

Additional Modification(s)

Add a Custom Keyboard

If you’re setting up your own Configurator, it’s likely that the reason for this is to test out your own keyboard firmware. The instructions below will explain how to do that.

It took me a lot of experimenting before I was able to get everything working. I wanted to avoid modifying any of the submodule files but found that some were unavoidable for various reasons.

Note: These instructions explain how to point the configurator to your own keyboard list. So the default list will no longer be available after doing these steps. The steps below are only necessary if that’s your intention.

Custom Keyboard Data

The configurator pulls both the list of keyboard and the individual keyboard info/readme files from keyboards.qmk.fm

These are the relevent endpoints and the structures to mimic:

keyboard_list.json

keyboards.qmk.fm/v1/keyboard_list.json

Structure:

1
2
3
4
5
6
7
{
   "keyboards":[
      "converter/ibmpc_usb/teensy",
      "converter/ibmpc_usb/promicro"
   ],
   "last_updated":"2022-09-07 03:45:37 GMT"
}

info.json

keyboards.qmk.fm/v1/keyboards/<keyboard>/info.json

Structure:

  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
185
186
187
188
189
190
191
192
193
194
{
   "last_updated":"2022-09-07 23:57:46 GMT",
   "keyboards":{
      "converter/ibmpc_usb/teensy":{
         "keyboard_name":"converter/ibmpc_usb/teensy",
         "keyboard_folder":"converter/ibmpc_usb/teensy",
         "layouts":{
            "LAYOUT_fullkey":{
               "layout":[
                  {"x":2.0,"y":0.0,"w":1.0,"label":"F13"},
                  {"x":3.0,"y":0.0,"w":1.0,"label":"F14"},
                  {"x":4.0,"y":0.0,"w":1.0,"label":"F15"},
                  {"x":5.0,"y":0.0,"w":1.0,"label":"F16"},
                  {"x":6.5,"y":0.0,"w":1.0,"label":"F17"},
                  {"x":7.5,"y":0.0,"w":1.0,"label":"F18"},
                  {"x":8.5,"y":0.0,"w":1.0,"label":"F19"},
                  {"x":9.5,"y":0.0,"w":1.0,"label":"F20"},
                  {"x":11.0,"y":0.0,"w":1.0,"label":"F21"},
                  {"x":12.0,"y":0.0,"w":1.0,"label":"F22"},
                  {"x":13.0,"y":0.0,"w":1.0,"label":"F23"},
                  {"x":14.0,"y":0.0,"w":1.0,"label":"F24"},
                  {"x":0.0,"y":1.0,"w":1.0,"label":"ESC"},
                  {"x":2.0,"y":1.0,"w":1.0,"label":"F1"},
                  {"x":3.0,"y":1.0,"w":1.0,"label":"F2"},
                  {"x":4.0,"y":1.0,"w":1.0,"label":"F3"},
                  {"x":5.0,"y":1.0,"w":1.0,"label":"F4"},
                  {"x":6.5,"y":1.0,"w":1.0,"label":"F5"},
                  {"x":7.5,"y":1.0,"w":1.0,"label":"F6"},
                  {"x":8.5,"y":1.0,"w":1.0,"label":"F7"},
                  {"x":9.5,"y":1.0,"w":1.0,"label":"F8"},
                  {"x":11.0,"y":1.0,"w":1.0,"label":"F9"},
                  {"x":12.0,"y":1.0,"w":1.0,"label":"F10"},
                  {"x":13.0,"y":1.0,"w":1.0,"label":"F11"},
                  {"x":14.0,"y":1.0,"w":1.0,"label":"F12"},
                  {"x":15.5,"y":1.0,"w":1.0,"label":"PSCR"},
                  {"x":16.5,"y":1.0,"w":1.0,"label":"SLCK"},
                  {"x":17.5,"y":1.0,"w":1.0,"label":"PAUS"},
                  {"x":20.0,"y":1.0,"w":1.0,"label":"VOLD"},
                  {"x":21.0,"y":1.0,"w":1.0,"label":"VOLU"},
                  {"x":22.0,"y":1.0,"w":1.0,"label":"MUTE"},
                  {"x":0.0,"y":2.5,"w":1.0,"label":"GRV"},
                  {"x":1.0,"y":2.5,"w":1.0,"label":"1"},
                  {"x":2.0,"y":2.5,"w":1.0,"label":"2"},
                  {"x":3.0,"y":2.5,"w":1.0,"label":"3"},
                  {"x":4.0,"y":2.5,"w":1.0,"label":"4"},
                  {"x":5.0,"y":2.5,"w":1.0,"label":"5"},
                  {"x":6.0,"y":2.5,"w":1.0,"label":"6"},
                  {"x":7.0,"y":2.5,"w":1.0,"label":"7"},
                  {"x":8.0,"y":2.5,"w":1.0,"label":"8"},
                  {"x":9.0,"y":2.5,"w":1.0,"label":"9"},
                  {"x":10.0,"y":2.5,"w":1.0,"label":"0"},
                  {"x":11.0,"y":2.5,"w":1.0,"label":"MINS"},
                  {"x":12.0,"y":2.5,"w":1.0,"label":"EQL"},
                  {"x":13.0,"y":2.5,"w":1.0,"label":"INT3"},
                  {"x":14.0,"y":2.5,"w":1.0,"label":"BSPC"},
                  {"x":15.5,"y":2.5,"w":1.0,"label":"INS"},
                  {"x":16.5,"y":2.5,"w":1.0,"label":"HOME"},
                  {"x":17.5,"y":2.5,"w":1.0,"label":"PGUP"},
                  {"x":19.0,"y":2.5,"w":1.0,"label":"NLCK"},
                  {"x":20.0,"y":2.5,"w":1.0,"label":"PSLS"},
                  {"x":21.0,"y":2.5,"w":1.0,"label":"PAST"},
                  {"x":22.0,"y":2.5,"w":1.0,"label":"PMNS"},
                  {"x":0.0,"y":3.5,"w":1.5,"label":"TAB"},
                  {"x":1.5,"y":3.5,"w":1.0,"label":"Q"},
                  {"x":2.5,"y":3.5,"w":1.0,"label":"W"},
                  {"x":3.5,"y":3.5,"w":1.0,"label":"E"},
                  {"x":4.5,"y":3.5,"w":1.0,"label":"R"},
                  {"x":5.5,"y":3.5,"w":1.0,"label":"T"},
                  {"x":6.5,"y":3.5,"w":1.0,"label":"Y"},
                  {"x":7.5,"y":3.5,"w":1.0,"label":"U"},
                  {"x":8.5,"y":3.5,"w":1.0,"label":"I"},
                  {"x":9.5,"y":3.5,"w":1.0,"label":"O"},
                  {"x":10.5,"y":3.5,"w":1.0,"label":"P"},
                  {"x":11.5,"y":3.5,"w":1.0,"label":"LBRC"},
                  {"x":12.5,"y":3.5,"w":1.0,"label":"RBRC"},
                  {"x":13.5,"y":3.5,"w":1.5,"label":"BSLS"},
                  {"x":15.5,"y":3.5,"w":1.0,"label":"DEL"},
                  {"x":16.5,"y":3.5,"w":1.0,"label":"END"},
                  {"x":17.5,"y":3.5,"w":1.0,"label":"PGDN"},
                  {"x":19.0,"y":3.5,"w":1.0,"label":"P7"},
                  {"x":20.0,"y":3.5,"w":1.0,"label":"P8"},
                  {"x":21.0,"y":3.5,"w":1.0,"label":"P9"},
                  {"x":22.0,"y":3.5,"w":1.0,"label":"PPLS"},
                  {"x":0.0,"y":4.5,"w":1.75,"label":"CAPS"},
                  {"x":1.75,"y":4.5,"w":1.0,"label":"A"},
                  {"x":2.75,"y":4.5,"w":1.0,"label":"S"},
                  {"x":3.75,"y":4.5,"w":1.0,"label":"D"},
                  {"x":4.75,"y":4.5,"w":1.0,"label":"F"},
                  {"x":5.75,"y":4.5,"w":1.0,"label":"G"},
                  {"x":6.75,"y":4.5,"w":1.0,"label":"H"},
                  {"x":7.75,"y":4.5,"w":1.0,"label":"J"},
                  {"x":8.75,"y":4.5,"w":1.0,"label":"K"},
                  {"x":9.75,"y":4.5,"w":1.0,"label":"L"},
                  {"x":10.75,"y":4.5,"w":1.0,"label":"SCLN"},
                  {"x":11.75,"y":4.5,"w":1.0,"label":"QUOT"},
                  {"x":12.75,"y":4.5,"w":1.0,"label":"NUHS"},
                  {"x":13.75,"y":4.5,"w":1.25,"label":"ENT"},
                  {"x":19.0,"y":4.5,"w":1.0,"label":"P4"},
                  {"x":20.0,"y":4.5,"w":1.0,"label":"P5"},
                  {"x":21.0,"y":4.5,"w":1.0,"label":"P6"},
                  {"x":22.0,"y":4.5,"w":1.0,"label":"PCMM"},
                  {"x":0.0,"y":5.5,"w":1.25,"label":"LSFT"},
                  {"x":1.25,"y":5.5,"w":1.0,"label":"NUBS"},
                  {"x":2.25,"y":5.5,"w":1.0,"label":"Z"},
                  {"x":3.25,"y":5.5,"w":1.0,"label":"X"},
                  {"x":4.25,"y":5.5,"w":1.0,"label":"C"},
                  {"x":5.25,"y":5.5,"w":1.0,"label":"V"},
                  {"x":6.25,"y":5.5,"w":1.0,"label":"B"},
                  {"x":7.25,"y":5.5,"w":1.0,"label":"N"},
                  {"x":8.25,"y":5.5,"w":1.0,"label":"M"},
                  {"x":9.25,"y":5.5,"w":1.0,"label":"COMM"},
                  {"x":10.25,"y":5.5,"w":1.0,"label":"DOT"},
                  {"x":11.25,"y":5.5,"w":1.0,"label":"SLSH"},
                  {"x":12.25,"y":5.5,"w":1.0,"label":"INT1"},
                  {"x":13.25,"y":5.5,"w":1.75,"label":"RSFT"},
                  {"x":16.5,"y":5.5,"w":1.0,"label":"UP"},
                  {"x":19.0,"y":5.5,"w":1.0,"label":"P1"},
                  {"x":20.0,"y":5.5,"w":1.0,"label":"P2"},
                  {"x":21.0,"y":5.5,"w":1.0,"label":"P3"},
                  {"x":22.0,"y":5.5,"w":1.0,"label":"PENT"},
                  {"x":0.0,"y":6.5,"w":1.25,"label":"LCTL"},
                  {"x":1.25,"y":6.5,"w":1.25,"label":"LGUI"},
                  {"x":2.5,"y":6.5,"w":1.25,"label":"LALT"},
                  {"x":3.75,"y":6.5,"w":1.25,"label":"INT5"},
                  {"x":5.0,"y":6.5,"w":2.75,"label":"SPC"},
                  {"x":7.75,"y":6.5,"w":1.25,"label":"INT4"},
                  {"x":9.0,"y":6.5,"w":1.25,"label":"INT2"},
                  {"x":10.25,"y":6.5,"w":1.25,"label":"RALT"},
                  {"x":11.5,"y":6.5,"w":1.25,"label":"RGUI"},
                  {"x":12.75,"y":6.5,"w":1.0,"label":"APP"},
                  {"x":13.75,"y":6.5,"w":1.25,"label":"LCTL"},
                  {"x":15.5,"y":6.5,"w":1.0,"label":"LEFT"},
                  {"x":16.5,"y":6.5,"w":1.0,"label":"DOWN"},
                  {"x":17.5,"y":6.5,"w":1.0,"label":"RGHT"},
                  {"x":19.0,"y":6.5,"w":2.0,"label":"P0"},
                  {"x":21.0,"y":6.5,"w":1.0,"label":"PDOT"},
                  {"x":22.0,"y":6.5,"w":1.0,"label":"PEQL"}
               ],
               "filename":"keyboards/converter/ibmpc_usb/ibmpc_usb.h",
               "c_macro":true
            },
         },
         "maintainer":"qmk",
         "manufacturer":"snacksthecat",
         "url":"",
         "usb":{
            "vid":"0xFEED",
            "pid":"0x6536",
            "device_version":"1.0.0",
            "device_ver":"0x0101"
         },
         "processor":"atmega32u4",
         "processor_type":"avr",
         "platform":"unknown",
         "protocol":"LUFA",
         "bootloader":"atmel-dfu",
         "mouse_key":{
            "enabled":true
         },
         "config_h_features":{
            "bootmagic":false,
            "mousekey":true,
            "extrakey":true,
            "console":true,
            "command":true,
            "nkro":true
         },
         "features":{
            "bootmagic":false,
            "mousekey":true,
            "extrakey":true,
            "console":true,
            "command":true,
            "nkro":true
         },
         "debounce":0,
         "indicators":{
            "caps_lock":"b6"
         },
         "tapping":{
            "term":175
         },
         "matrix_size":{
            "cols":16,
            "rows":8
         },
         "split":{
            "transport":{
               "protocol":"i2c"
            }
         }
      }
   }
}

readme.md

1
keyboards.qmk.fm/v1/keyboards/<keyboard>/readme.md

Structure:

(Just a normal readme.md file)

Explained

The easiest way I could think to serve these files up was to put them in a GitHub repositor with a folder structure mimicking the API path that the configurator is expecting.

Your JSON files or APIs will need to be accessible following the URL patterns above. So If I wanted to serve up this JSON data from GitHub, I would need to follow the URL pattern, which directs to the raw text content of an uploaded file:

https://raw.githubusercontent.com/snacksthecat/qmk_web_stack_files/main/v1/keyboards/<keyboard_name>/info.json
|--------------------------base url------------------------------------|----path----|----kb name----|--file--| 

qmk_configurator

Dockerfile

Once I had my APIs setup, I added the necessary environment variable to the qmk_configurator Dockerfile so that it gets picked up by the constants declaration file

ENV VITE_API_URL=/api
ENV VITE_KEYBOARDS_URL=https://raw.githubusercontent.com/snacksthecat/qmk_web_stack_files/main

Define a Default Layout

If you were to spin up the configurator at this point, it should load, but with a totally blank layout. The steps below explain how to create a default layout that gets picked up by the configurator when the keyboard first loads.

qmk_configurator

public/keymaps

The default keymaps for the configurator are all located in:

/qmk_web_stack/qmk_configurator/public/keymaps/

They’re organized by letter, so navigate into the folder with the first letter of your keyboard’s name.

You’ll need to create a new file with a filename that follows this pattern:

<keyboard_name>_default.json

Note: If your keyboard name has slashes in it, you’ll need to replace them with underscores. For instance, my default filename was: converter_ibmpc_usb_teensy_default.json

Below is an example of the contents of one of these default layout files (excuse the formatting):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
{
  "keyboard": "converter/ibmpc_usb/teensy",
  "keymap": "default",
  "commit": "c8ae1cf59b4a2b23a3db6fb638a122ca7b39f5ec",
  "layout": "LAYOUT_fullkey",
  "layers": [
    [
                   "KC_F13", "KC_F14", "KC_F15", "KC_F16", "KC_F17", "KC_F18", "KC_F19", "KC_F20", "KC_F21", "KC_F22", "KC_F23", "KC_F24",
"KC_ESC",        "KC_F1",  "KC_F2",  "KC_F3",  "KC_F4",  "KC_F5",  "KC_F6",  "KC_F7",  "KC_F8",  "KC_F9",  "KC_F10", "KC_F11", "KC_F12", "KC_PSCR", "KC_SLCK", "QK_BOOT", "KC_VOLD", "KC_VOLU", "KC_MUTE",

"KC_GRV", "KC_1", "KC_2", "KC_3", "KC_4", "KC_5", "KC_6", "KC_7", "KC_8", "KC_9", "KC_0", "KC_MINS", "KC_EQL", "KC_INT3", "KC_BSPC", "KC_INS", "KC_HOME", "KC_PGUP", "KC_NLCK", "KC_PSLS", "KC_PAST", "KC_PMNS",
"KC_TAB", "KC_Q", "KC_W", "KC_E", "KC_R", "KC_T", "KC_Y", "KC_U", "KC_I", "KC_O", "KC_P", "KC_LBRC", "KC_RBRC", "KC_BSLS", "KC_DEL", "KC_END", "KC_PGDN", "KC_P7", "KC_P8", "KC_P9", "KC_PPLS",
"KC_CAPS", "KC_A", "KC_S", "KC_D", "KC_F", "KC_G", "KC_H", "KC_J", "KC_K", "KC_L", "KC_SCLN", "KC_QUOT", "KC_NUHS", "KC_ENT", "KC_P4", "KC_P5", "KC_P6", "KC_PCMM",
"KC_LSFT", "KC_NUBS", "KC_Z", "KC_X", "KC_C", "KC_V", "KC_B", "KC_N", "KC_M", "KC_COMM", "KC_DOT", "KC_SLSH", "KC_INT1", "KC_RSFT", "KC_UP", "KC_P1", "KC_P2", "KC_P3", "KC_PENT",
"KC_LCTL", "KC_LGUI", "KC_LALT", "KC_INT5", "KC_SPC", "KC_INT4", "KC_INT2", "KC_RALT", "KC_RGUI", "KC_APP", "KC_RCTL", "KC_LEFT", "KC_DOWN", "KC_RGHT", "KC_P0", "KC_PDOT", "KC_PEQL"
    ]
  ]
}

The “layers” node is basically a JSON-ified version of your keymap.c file.

You can read more about the specifics of the default keymap file on the QMK website. It’s the same file that you can download/upload to the configurator to save/load your keymap.

Docker Compose

Finally it’s time to build and launch the containers which should bring your configurator online and make everything work end-to-end with all of your changes.

Navigate to the main project directory:

cd ~\qmk_web_stack

Run the following command to build all of the Docker containers. It will take some minutes.

docker compose build

Hopefully it builds OK. If so, launch all of the containers using the following command:

docker compose up

You’ll want to keep an eye on the log output and make sure that all service workers get launched. If you see some ugly errors, most likely one of these failed to launch and you’ll need to backtrack.

Now you should be able to visit your configurator at: http://<your_url>:5000

QMK Configurator

Read Part 2 of this guide to setup a reverse proxy and SSL.

Built with Hugo
Theme Stack designed by Jimmy