diff --git a/tools/docker-build/Dockerfile b/tools/docker-build/Dockerfile
new file mode 100644
index 0000000000000..c4f1dd76c073d
--- /dev/null
+++ b/tools/docker-build/Dockerfile
@@ -0,0 +1,46 @@
+FROM ubuntu:24.04 as build
+
+ARG ARM_TOOLCHAIN_URL=https://developer.arm.com/-/media/Files/downloads/gnu/13.2.rel1/binrel/
+
+ARG ARM_TOOLCHAIN_FILE=arm-gnu-toolchain-13.2.rel1-x86_64-arm-none-eabi.tar.gz
+
+ARG ARM_TOOLCHAIN_DIR=arm-gnu-toolchain-13.2.Rel1-x86_64-arm-none-eabi
+
+ARG TOOLS_DIR=/tools
+
+ARG DOWNLOAD_DIR=${TOOLS_DIR}/bin
+
+RUN apt-get update && apt-get install -y build-essential software-properties-common git git-lfs gettext cmake mtools wget curl which
+
+RUN mkdir -p ${DOWNLOAD_DIR} && cd ${DOWNLOAD_DIR} && curl -LO ${ARM_TOOLCHAIN_URL}/${ARM_TOOLCHAIN_FILE}
+
+RUN cd ${DOWNLOAD_DIR} && pwd && tar -xf ${ARM_TOOLCHAIN_FILE} && rm ${ARM_TOOLCHAIN_FILE}
+
+ENV PATH="${PATH}:${DOWNLOAD_DIR}/${ARM_TOOLCHAIN_DIR}/bin"
+
+RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -y | sh
+
+RUN apt-get install -y git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build
+
+RUN apt-get install -y ccache libffi-dev libssl-dev dfu-util libusb-1.0-0 python-is-python3
+
+ENV IDF_TOOLS_PATH=${TOOLS_DIR}/.espressif
+
+ENV IDF_PATH=${TOOLS_DIR}/esp/esp-idf
+
+RUN mkdir -p ${TOOLS_DIR}/esp && cd ${TOOLS_DIR}/esp && git clone -b v5.2.2 --recursive https://github.com/espressif/esp-idf.git
+
+RUN cd ${TOOLS_DIR}/esp/esp-idf && ./install.sh all
+
+RUN curl -LO https://raw.githubusercontent.com/adafruit/circuitpython/main/requirements-dev.txt
+
+RUN curl -LO https://raw.githubusercontent.com/adafruit/circuitpython/main/requirements-doc.txt
+
+RUN . ${TOOLS_DIR}/esp/esp-idf/export.sh && pip3 install --upgrade -r requirements-dev.txt && \
+           pip3 install --upgrade -r requirements-doc.txt
+
+COPY build.sh ${TOOLS_DIR}
+
+RUN chmod -R a=u ${TOOLS_DIR}
+
+ENTRYPOINT ["/bin/bash"]
diff --git a/tools/docker-build/README.md b/tools/docker-build/README.md
new file mode 100644
index 0000000000000..2400d8823c296
--- /dev/null
+++ b/tools/docker-build/README.md
@@ -0,0 +1,22 @@
+# Building CircuitPython with Docker
+
+On a Linux machine with `docker` installed, an executable for any supported CircuitPython board can be built without setup by invoking the command `tools/docker-build/build <port> <board-name> <option>` from the CircuitPython root directory.  For example, if the CircuitPython repo is located at `~/circuitpython` the commands
+
+```bash
+    cd ~/circuitpython
+    tools/docker-build/build espressif adafruit_feather_esp32s2_reverse_tft
+    tools/docker-build/build raspberrypi waveshare_rp2040_zero
+    tools/docker-build/build atmel-samd grandcentral_m4_express DEBUG=1 TRANSLATION=es
+```
+
+will build executables for a Feather ESP32S2 board, a Waveshare RP2040 board and an Adafruit Grand Central M4 Express board, and the Grand Central build will include debugging information and Spanish localization.
+
+## Building a new Docker image
+
+The `build` script uses tools built into a container image pulled from a public Docker Hub repository.  If you like, you can build your own image from the `Dockerfile` in this directory with the command
+
+```bash
+    docker build -t <name> .
+```
+
+Where <name> is the name that will be applied to the image.  If you then change the value of `$LOCAL_IMAGE` in the `build` script to match <name>, `build` will use the new image for building CircuitPython.
diff --git a/tools/docker-build/build b/tools/docker-build/build
new file mode 100755
index 0000000000000..1ab82d2404857
--- /dev/null
+++ b/tools/docker-build/build
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+REMOTE_IMAGE="timchinowsky/build_circuitpython"
+
+LOCAL_IMAGE=$REMOTE_IMAGE
+
+make fetch-all-submodules
+
+docker pull $REMOTE_IMAGE
+
+docker run --user $UID:$(id -g) -it -v .:/circuitpython $LOCAL_IMAGE /tools/build.sh "$@"
diff --git a/tools/docker-build/build.sh b/tools/docker-build/build.sh
new file mode 100644
index 0000000000000..bbeb8abac0f38
--- /dev/null
+++ b/tools/docker-build/build.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+git config --global --add safe.directory /circuitpython
+
+git config --global --add safe.directory /circuitpython/ports/espressif/esp-idf
+
+cd /circuitpython
+
+make fetch-tags
+
+. /tools/esp/esp-idf/export.sh
+
+cd /circuitpython/ports/$1
+
+make BOARD=${@:2}