Docker is weird
Контейнеры — это не виртуальные машины, а обычные процессы, запущенные ядром хоста с особыми ограничениями. Их можно найти даже в /proc. При этом в Linux нет отдельной функции «контейнеры»: это набор из пяти механизмов ядра — пространства имён скрывают видимость, cgroups ограничивают ресурсы, а сеть изолируется отдельной магией.
Образы — это просто папки, которые Docker куда-то кладёт и делает их новым корнем процесса через chroot или pivot_root. При запуске docker run -it --rm ubuntu /bin/bash Docker скачивает образ, размещает его и выполняет /some/weird/docker/path/bin/bash.
Любопытно, что Go плохо подходит для среды выполнения контейнеров. runc написан на Go, но его многопоточная среда мешает: многие системные вызовы требуют однопоточности, поэтому перед инициализацией Go запускается специальный модуль на C. Кроме того, в Go нет полноценного fork — он копирует только главный поток, поэтому runc обходит это через запуск /proc/self/exe с особым аргументом. Наконец, Docker обычно требует прав root, так как многие API ядра исторически доступны только суперпользователю; без root приходится переписывать весь сетевой стек в пользовательском пространстве.