diff --git a/Makefile.include b/Makefile.include
index 951b6e96bc4db8147cf6c917779459517b3c0d1b..7235312eb3fdb5ada9c330155f461dc144252a1a 100644
--- a/Makefile.include
+++ b/Makefile.include
@@ -10,6 +10,12 @@ RIOTCPU := $(abspath $(RIOTCPU))
 RIOTBOARD ?= $(RIOTBASE)/boards
 RIOTBOARD := $(abspath $(RIOTBOARD))
 
+RIOTPROJECT ?= $(shell git rev-parse --show-toplevel 2>/dev/null || pwd)
+RIOTPROJECT := $(abspath $(RIOTPROJECT))
+
+# Path to the current directory relative to the git root
+BUILDRELPATH ?= $(shell git rev-parse --show-prefix)
+
 BINDIRBASE ?= $(CURDIR)/bin
 BINDIRBASE := $(abspath $(BINDIRBASE))
 
@@ -145,6 +151,7 @@ BASELIBS += $(BINDIR)${APPLICATION}.a
 BASELIBS += $(USEPKG:%=${BINDIR}%.a)
 
 .PHONY: all clean flash term doc debug debug-server reset objdump help
+.PHONY: ..in-docker-container
 
 ELFFILE ?= $(BINDIR)$(APPLICATION).elf
 HEXFILE ?= $(ELFFILE:.elf=.hex)
@@ -157,6 +164,9 @@ LINKFLAGPREFIX ?= -Wl,
 
 DIRS += $(EXTERNAL_MODULE_DIRS)
 
+ifeq ($(BUILD_IN_DOCKER),1)
+all: ..in-docker-container
+else
 ## make script for your application. Build RIOT-base here!
 all: ..compiler-check ..build-message $(USEPKG:%=${BINDIR}%.a) $(APPDEPS)
 	$(AD)DIRS="$(DIRS)" "$(MAKE)" -C $(CURDIR) -f $(RIOTBASE)/Makefile.application
@@ -169,6 +179,7 @@ endif
 	$(AD)$(SIZE) $(ELFFILE)
 	$(AD)$(OBJCOPY) $(OFLAGS) $(ELFFILE) $(HEXFILE)
 endif
+endif # BUILD_IN_DOCKER
 
 ..compiler-check:
 	$(AD)command -v $(CC) >/dev/null 2>&1 || \
@@ -260,6 +271,36 @@ objdump:
 		exit 1; }
 	$(PREFIX)objdump -S -D -h $(ELFFILE) | less
 
+export DOCKER_IMAGE ?= riot/riotbuild:latest
+export DOCKER_BUILD_ROOT ?= /data/riotbuild
+export DOCKER_FLAGS ?= --rm
+# Default target for building inside a Docker container
+export DOCKER_MAKECMDGOALS ?= all
+# This will execute `make $(DOCKER_MAKECMDGOALS)` inside a Docker container.
+# We do not push the regular $(MAKECMDGOALS) to the container's make command in
+# order to only perform building inside the container and defer executing any
+# extra commands such as flashing or debugging until after leaving the
+# container.
+# The `flash`, `term`, `debugserver` etc. targets usually require access to
+# hardware which may not be reachable from inside the container.
+..in-docker-container:
+	@$(COLOR_ECHO) '${COLOR_GREEN}Launching build container using image "$(DOCKER_IMAGE)".${COLOR_RESET}'
+	docker run $(DOCKER_FLAGS) -i -t -u "$$(id -u)" \
+	    -v '$(RIOTBASE):$(DOCKER_BUILD_ROOT)/riotbase' \
+	    -v '$(RIOTCPU):$(DOCKER_BUILD_ROOT)/riotcpu' \
+	    -v '$(RIOTBOARD):$(DOCKER_BUILD_ROOT)/riotboard' \
+	    -v '$(RIOTPROJECT):$(DOCKER_BUILD_ROOT)/riotproject' \
+	    -e 'RIOTBASE=$(DOCKER_BUILD_ROOT)/riotbase' \
+	    -e 'RIOTCPU=$(DOCKER_BUILD_ROOT)/riotcpu' \
+	    -e 'RIOTBOARD=$(DOCKER_BUILD_ROOT)/riotboard' \
+	    -e 'RIOTPROJECT=$(DOCKER_BUILD_ROOT)/riotproject' \
+	    -e 'BOARD=$(BOARD)' \
+	    -e 'RIOT_VERSION=$(RIOT_VERSION)' \
+	    -e '__RIOTBUILD_FLAG=$(__RIOTBUILD_FLAG)' \
+	    -e 'QUIET=$(QUIET)' \
+	    -w '$(DOCKER_BUILD_ROOT)/riotproject/$(BUILDRELPATH)' \
+	    '$(DOCKER_IMAGE)' make $(DOCKER_MAKECMDGOALS)
+
 # Extra make goals for testing and comparing changes.
 include $(RIOTBASE)/Makefile.buildtests
 
diff --git a/Makefile.vars b/Makefile.vars
index 7896e67a0abcab8b538f90268acd16c23dd728af..ab69f06e77547d458e51a02472b0d2cdbbf2b7a7 100644
--- a/Makefile.vars
+++ b/Makefile.vars
@@ -16,6 +16,7 @@ export APPDEPS               # Files / Makefile targets that need to be created
 export RIOTBASE              # The root folder of RIOT. The folder where this very file lives in.
 export RIOTCPU               # For third party CPUs this folder is the base of the CPUs.
 export RIOTBOARD             # For third party BOARDs this folder is the base of the BOARDs.
+export RIOTPROJECT           # Top level git root of the project being built, or PWD if not a git repository
 export BINDIRBASE            # This is the folder where the application should be built in. For each BOARD a different subfolder is used.
 export BINDIR                # This is the folder where the application should be built in.
 
diff --git a/dist/gdbinit-docker b/dist/gdbinit-docker
new file mode 100644
index 0000000000000000000000000000000000000000..a3e4f1b39cb23da12fca995fc930e541c74f0642
--- /dev/null
+++ b/dist/gdbinit-docker
@@ -0,0 +1,36 @@
+# Set some path substitution rules to be able to list source code while
+# debugging after the build was run inside a Docker container
+# These are harmless as they only apply to filenames beginning with /data/riotbuild/
+python
+import os
+try:
+    path = os.environ['RIOTBASE']
+except KeyError:
+    # Environment variable wasn't set.
+    pass
+else:
+    gdb.execute('set substitute-path /data/riotbuild/riotbase ' + path)
+try:
+    path = os.environ['RIOTCPU']
+except KeyError:
+    # Environment variable wasn't set.
+    pass
+else:
+    gdb.execute('set substitute-path /data/riotbuild/riotcpu ' + path)
+try:
+    path = os.environ['RIOTBOARD']
+except KeyError:
+    # Environment variable wasn't set.
+    pass
+else:
+    gdb.execute('set substitute-path /data/riotbuild/riotboard ' + path)
+try:
+    path = os.environ['RIOTPROJECT']
+except KeyError:
+    # Environment variable wasn't set.
+    pass
+else:
+    gdb.execute('set substitute-path /data/riotbuild/riotproject ' + path)
+end
+
+show substitute-path