Compile Marlin (3D Printer Firmware) with Docker

Goal: Compile Marlin 2.x from source using just Docker and Linux without the Arduino IDE nor VSCode. Let’s simply run a Bash script and see a firmware BIN file appear.
Goal 2: Build a thermistor temperature table for an unknown hotend thermistor and compile it into Marlin.

Let’s see what we are working with from Usually to compile Marlin you must do one of two unfortunate things:

The first thing you’ll need to do is download Arduino IDE and install it following the usual procedure for your OS. (REF) [or] Install (Python and) PlatformIO as a stand alone CLI or with your choice of environment. (REF)

Do you want to install the Arduino IDE and build tools and extra libraries and on an on? Or install Python and another heavy library?

Wouldn’t it be nice if we had a Docker container to just build the binary firmware file and gift-wrap it for us with a single command without installing Arduino, VSCode, build tools, Python, or the like?

Let’s take a look at Marlin-2.1.x – the current release at this time.

Some interesting folders and files
Some interesting folders and files

Neat. There is a Makefile and some Docker artifacts. Can we use these to automate building the firmware.bin? Let’s start with downloading the latest source and machine configurations to an IDE (not VSCode) and explore the file structures.

Source and configurations
Source and configurations

Digging around, I see Marlin tests are run using PlatformIO, which is new to me, but it is included in the Dockerfile. Let’s explore it and figure out how to use it.

PlatformIO can be run from the CLI
PlatformIO can be run from the CLI

Something is missing from the Zip file from but is included in the GitHub repo: the workflows/ folder and its YAML files. This is apparent when looking at the

On GitHub, those test targets are these:

Get the platform strings for PlatformIO from GitHub
Get the platform strings for PlatformIO from GitHub

Fortunately, they match up with the buildroot/tests files in the repo. I had to double-check.

The ones most interesting for the Ender 3 crowd are:

  • STM32F103RE_creality
  • STM32F103RC_btt

What do the PlatformIO board strings mean?

I suspected that STM32F103RE_creality is not a platform, but STM32F103RE is. If we exclude what we know about tests, and investigate the PlatformIO boards, the boards are simpler strings.

Sample PlatformIO boards
Sample PlatformIO boards

However, this gives a compile error:

Error: Build environment ‘STM32F103RE’ is incompatible with BOARD_CREALITY_V427. Use one of these: STM32F103RE_creality, STM32F103RE_creality_xfer, …

Why is this? Because, platformio.ini in the Marlin/ folder holds included ini files to the recognized platforms, in actuality.

PlatformIO environments
PlatformIO environments

Let’s compile some firmware

We’ll give compiling the firmware a shot using STM32F103RE_creality.

Right away we see a compile error.

PlatformIO error on compile
PlatformIO error on compile

Let’s figure out how to direct PlatformIO to use custom configurations for Ender machines. Forgive me for trying this for the first time as I write this.

The configuration in the Marlin folder is used
The configuration in the Marlin folder is used

Well of course the Configuration.h et al. is coming from the C++ source folder (i.e. Marlin/). Still, we don’t want to edit those template files manually when we have config files that the community has provided (and we can further adjust) outside the Git repo.

Could we use a script to overwrite them from an external folder on compile? Use some symlinks? What do the platform tests do, I wonder? Back to the Makefile.

We then trace run_tests . $(TEST_TARGET) to

PlatformIO doesn’t know about Configuration.h

The build command didn’t use any arguments to pass in configuration files. Let’s see what other plumbing there is.

I see it now: On every platform test, there is a request to download the configuration files for a given platform directly to the Marlin source folder, then after the test, a git reset --hard is performed to undo the downloads. Interesting approach at integrating two tightly-coupled Git repos.

Let’s just copy over the zipped config files to Marlin/ and recompile and see what happens.

And therein highlights the difficulties in maintaining two tightly-coupled Git repos. Let’s dump what we’ve downloaded previously from a Zip file and curl the bugfix-2.1.x repo then recompile. We’ll need to engineer a way to keep these versions in sync in a compile script.

Successful Marlin build
Successful Marlin build
Firmware is compiled
Firmware is compiled

Hotend/extruder fan gotcha with the 4.2.7 board

For the Ender using the Creality 4.2.7 board, I ran into a gotcha I can share.

In Configuration_adv.h, the line #define E0_AUTO_FAN_PIN -1 means that the hotend/extruder fan is always on because automatic control is disabled (-1). That’s silly. Before, the extruder fan would come on automatically at 50℃ with the old board and the BTT boards. However, as if Marlin knows we want a better life for ourselves, when we try to fix it with #define E0_AUTO_FAN_PIN FAN_PIN, we are presented with this error:

error: #error “You cannot set E0_AUTO_FAN_PIN equal to FAN_PIN.”

Why is this? From digging around the Marlin source (pins_CREALITY_V4.h) and looking at the v4.2.7 mainboard schematic, we quickly see that not only are both k-fan ports just hooked up in parallel, but also the “normal fan” terminal connection is wired to the power supply.

Creality 4.2.7 mainboard with fans highlighted
Creality 4.2.7 mainboard with fans highlighted

This means we are hooped on controlling the hotend/extruder fan automatically. Dang. Better invest in a quiet Noctura fan with this board.

Tip: Use the normal fan screw terminals to also power your mainboard fan so it is always on. In other words, do not use the k-fan JST port or else the mainboard fan will only come on when the part-cooling fan is on, which may be never when printing ABS (and you may fry your mainboard).

BLTouch/3D Touch z-stop gotcha with the 4.2.7 board

Some online guides for configuring Configuration.h suggest the setting #define Z_MIN_PROBE_PIN 17. No. leave Z_MIN_PROBE_PIN commented out in Configuration.h because in pins_CREALITY_V4.h we see

For the 4.2.7 board, is this PB1 pin correct? Yes. Here is the schematic.

Creality 4.2.7 schematic for BLTouch probe pin
Creality 4.2.7 schematic for BLTouch probe pin

BLTouch/3D Touch still does not stop on z-axis homing?

Sometimes who knows what is going on with bugfix-2.x and we’ve caught a bad build, or Creality made the 5-pin JST port only work with a CR Touch? The board fried a little bit when you put the pins in backwards? Who knows? The 5-pin dedicated BLTouch port should work. If you’ve tested the wiring with an ohmmeter to rule out shorts, and are sure your 5-wire wiring is spot on, then cut your time loss and drop to plan B: Use the z-axis endstop JST port as show in the Teaching Tech guide. Remember to update your Configuration.h to use Z_MIN_PROBE_USES_Z_MIN_ENDSTOP_PIN and not USE_PROBE_FOR_Z_HOMING.

BLTouch/3D Touch wiring for the Creality 4.2.7 mainboard
BLTouch/3D Touch wiring for the Creality 4.2.7 mainboard

Something interesting to note is that the BLTouch probe’s OUT pin connects directly to the CPU, whereas the endstop has some current-limiting functionality. It’s possible the clone 3D Touch might not work but a genuine BLTouch works? Who is to say? Here is the schematic of the differences between the z-min endstop and the dedicated BLTouch endstop.

Creality 4.2.7 mainboard with z-min protection highlighted
Creality 4.2.7 mainboard with z-min protection highlighted

Hotend thermistor reporting the wrong temperature?

I picked-up a modded secondhand printer with a 10kΩ thermistor (too inaccurate), so I bought a 10-pack of popular NTC 100kΩ thermistors from Amazon. However, the PLA filament wasn’t melting at 210℃; a thermal camera showed that “210℃” was in fact 165℃. The seller advertised to use TEMP_SENSOR 11 in Marlin, but this was also inaccurate.

Want to learn to build your own thermal curve table in Marlin? Here is a fun guide if you already have the ADC data. If you don’t, or have trust issues or are a perfectionist, you can make your own table with a temperature probe on a multimeter. First, enable this:

Using OctoPrint or Pronterface, we can send temperature G-codes and observe the ADC values.

Temperature ADC measurements with Pronterface
Temperature ADC measurements with Pronterface

With a bit of macro magic in Notepad++, and remembering to divide by 4 and sort by descending temperature, here is my temperature table after about half an hour of work:

Warning: Marlin does not extrapolate the temperature curve from the table; it interpolates ADC values between known points. That means we need a zero crossing, and we must measure the highest temperature we plan to use. In the above case, we cannot exceed 255℃ or a heating error will occur (measure higher temperature points in that case), so I added the 938℃ just in case. Also, the heater may not keep up past 250℃ leading to a thermal runaway error.

This is a good time to dial in your extruder E-steps

If you update your firmware, you might have upgraded your extruder from Bowden tube to direct-drive, for example. You’ll want to calibrate your extruder E-steps. As a quick reminder:

  1. Heat up your nozzle to 210℃ for PLA.
  2. Extrude a bit of filament.
  3. Wait for the filament to stop oozing out.
  4. Mark out 120mm of filament with calipers and a Sharpie.
  5. Raise the nozzle to about 20cm for a nice, straight extrusion.
  6. Use Pronterface to send the G-code G1 E100 F100 to extrude 100mm.
  7. Measure how much filament is remaining.
    e.g. 105.94mm on a geared extruder, yikes
  8. Issue G-code M503 and find the row starting with M92 for the current E-steps.
    e.g. E93.00
  9. Subtract (7) from 100mm.
    e.g. 120mm – 105.94mm = 14.06mm extruded
  10. Take (8) and multiply by 100mm, then divide by the actual length extruded.
    e.g. (93.00*100)/14.06 = 661.5 steps/mm
  11. Send G-code M92 E###.#.
    e.g. M92 E661.5
  12. Send G-code M500 to save this value.
  13. Repeat steps 4 to 12 one more time, or stop when you get exactly 100mm extruded.

In my case with my secondhand, modded Ender 3 printer with a dual-gear extruder, I performed the above procedure three times to get exactly 100mm of extrusion at E406.5 steps/mm.

How to get the new firmware on the mainboard?

Great. What’s next?

Creality took away the ISP port for the V4.2.7 but pre-installed a bootloader on the board so that you can update firmware just with the microSD card. On the new boards, all you have to do is include the firmware BIN file on the microSD card, and the printer will update itself! (REF)


Go to your “Marlin-2.0.x” folder, and find and open the “.pio” folder. Open the build folder, then open the folder with a name beginning with “STM32” (names might vary depending on the mainboard). Find and copy the most recent (by time) BIN file. Make sure not to change the firmware’s name, as it won’t work if the name is changed. (REF)

Let’s see if I can copy that file to an SD card and if my Ender 3 Pro with a 4.2.7 board takes it.

Success (mostly): Ender 3 reports that Marlin 2.1.1 is present, the mainboard is 4.2.7, but on a power cycle there is a persistent EEPROM message about resetting the version, but “ignore” and “reset” don’t seem to affect much of anything. I’ll circle back to that.
EEPROM Reset Fixes: 1) Press “reset”, remove the SD card, and power cycle. OR 2) Send the machine an M502 (factory reset) and then M500 (save settings) to write the current setting in firmware to the EEPROM. OR 3) Go into the menu under Configuration > Advanced Configuration > Initialize EEPROM.

Let’s make a Marlin firmware compile script in Bash

I’ve made a simple Bash script that clones Marlin to a temp folder, copies the configuration files for a given board for a clean slate, copies any existing user configuration files instead, and compiles the firmware. Here is the result.

Successful run of the compile script
Successful run of the compile script

Marlin Firmware Compile Script

Here is my working script. There are only three strings to be edited for your given board. We can run it over and over again and newer and newer firmware files will be created. Feel free to make Configuration.h tweaks, recompile, test on hardware, and repeat until happy. Feel free to fork and improve on this script.

If you share this script, kindly attribute it. Thank you.

Copy script to clipboard


With a bit of reverse-engineering, we’ve figured out how to easily compile the Marlin firmware without the need for installing any of VSCode, Arduino, Python, PlatformIO, and instead leveraged the existing testing framework of the Marlin team to invoke Docker to perform all the magic for us, wrapped up in a single Bash script.

Success: With the Bash script above, and Docker, we can completely compile the latest Marlin firmware to a BIN file that can be loaded on an SD card and flashed to a newer 32-bit mainboard like the Creality 4.2.7 and the BigTreeTech SKR series.