diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..e68b32bc5244f384bc2c3aa9775ddfa64b1c0b80
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,69 @@
+FROM docker.io/debian:bookworm AS toolchain
+RUN apt update && apt upgrade -y
+RUN apt install -y \
+  build-essential \
+  git autoconf \
+  automake \
+  autotools-dev \
+  libncurses5-dev \
+  gperf \
+  bison \
+  flex \
+  texinfo \
+  help2man \
+  gawk \
+  wget \
+  subversion \
+  bzip2 \
+  libtool-bin \
+  unzip
+RUN mkdir -p /ctng
+WORKDIR /ctng
+# crosstool-ng doesn't want to run as root
+RUN useradd -ms /bin/bash builder
+RUN chown -R builder:builder /ctng
+USER builder
+RUN git clone https://github.com/crosstool-ng/crosstool-ng
+WORKDIR /ctng/crosstool-ng
+COPY no-ulimit.patch .
+RUN git apply no-ulimit.patch
+RUN ./bootstrap && \
+  ./configure --enable-local && \
+  make -j$(nproc)
+RUN ./ct-ng riscv32-unknown-elf
+RUN ./ct-ng build
+
+
+FROM docker.io/debian:bookworm AS qemu
+RUN apt update && apt upgrade -y
+RUN apt install -y \
+  build-essential \
+  git \
+  python3 python3-pip python3-venv \
+  python3-sphinx \
+  libglib2.0-dev \
+  libfdt-dev \
+  libpixman-1-dev \
+  zlib1g-dev \
+  ninja-build \
+  libnfs-dev \
+  libiscsi-dev
+RUN mkdir -p /qemu
+WORKDIR /qemu
+RUN git clone https://git.imp.fu-berlin.de/projekt-telematik-ss23-qemu/risc-v-qemu.git && \
+   cd risc-v-qemu && \
+   git checkout esp32_c3_bringup && \
+   mkdir -p build && cd build && \
+   ../configure --prefix=/usr/local/ --target-list=riscv32-softmmu && \
+   make -j$(nproc) && \
+   make install
+
+
+FROM docker.io/debian:bookworm AS final
+# Install qemu's runtime deps
+RUN apt update && apt upgrade -y
+RUN apt install -y libpixman-1-0 libfdt1 libglib2.0-0 libiscsi7 libnfs13
+# Install additional developer tools
+RUN apt install -y gdb-multiarch make
+COPY --from=qemu /usr/local /usr/local
+COPY --from=toolchain /home/builder/x-tools/riscv32-unknown-elf/* /usr/local/bin/
diff --git a/no-ulimit.patch b/no-ulimit.patch
new file mode 100644
index 0000000000000000000000000000000000000000..3bba53c2015fa99a917aca19b31cf7339197d3b3
--- /dev/null
+++ b/no-ulimit.patch
@@ -0,0 +1,19 @@
+diff --git a/scripts/crosstool-NG.sh b/scripts/crosstool-NG.sh
+index 383de66d..1223e5ef 100644
+--- a/scripts/crosstool-NG.sh
++++ b/scripts/crosstool-NG.sh
+@@ -104,14 +104,6 @@ for d in            \
+         esac
+ done
+ 
+-n_open_files=$(ulimit -n)
+-if [ "${n_open_files}" -lt 2048 ]; then
+-    # Newer ld seems to keep a lot of open file descriptors, hitting the default limit
+-    # (1024) for example during uClibc-ng link.
+-    CT_DoLog WARN "Number of open files ${n_open_files} may not be sufficient to build the toolchain; increasing to 2048"
+-    ulimit -n 2048
+-fi
+-
+ # Where will we work?
+ CT_WORK_DIR="${CT_WORK_DIR:-${CT_TOP_DIR}/.build}"
+ CT_DoExecLog ALL mkdir -p "${CT_WORK_DIR}"