docker-unlocker
Run Linux Docker containers on Windows from one app: a hidden Alpine VM in bundled QEMU + WHPX, with one-click app installs and a built-in terminal.
- Active · last change 10-10-2026
- ★ 1 on GitHub
README
Docker Unlocker
Run Linux Docker containers on Windows from one small Lazarus app. You don't need WSL, Docker Desktop or a Linux install you manage yourself.
Docker Unlocker carries its own hidden Linux environment: a tiny Alpine Linux VM
run by QEMU and accelerated by the Windows Hypervisor Platform (WHPX). You start
and stop it with one button. Docker runs inside it, and the GUI (or the normal
docker CLI) talks to it over a port bound to 127.0.0.1.
??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
??? DockerUnlocker.exe (Lazarus / LCL) ???
??? containers ?? images ?? console ?? log ???
??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
??? Docker API ??? QMP ??? cloud-init seed (first boot)
??? :23750 ??? :23751 ??? :23752
??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????
??? qemu-system-x86_64 -accel whpx ???
??? ???????????????????????????????????????????????????????????????????????????????????????????????????????????? ???
??? ??? Alpine Linux + dockerd (:2375) ??? ???
??? ??? vda: system overlay (resettable) ??? ???
??? ??? vdb: /var/lib/docker (your data) ??? ???
??? ???????????????????????????????????????????????????????????????????????????????????????????????????????????? ???
??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????Requirements
-
Windows 10/11 x64.
-
QEMU for Windows (x86_64), for example from https://qemu.weilnetz.de/w64/. Docker Unlocker looks for it in this order:
- the folder set in Settings
- a
qemu\folder next toDockerUnlocker.exe(the bundled layout) C:\Program Files\qemu
-
Windows Hypervisor Platform, for speed. Without it QEMU falls back to software emulation (TCG), which works but is very slow. To enable it, run this in an elevated PowerShell, then reboot:
Enable-WindowsOptionalFeature -Online -FeatureName HypervisorPlatformVirtualization (AMD-V / Intel VT-x) must also be on in the BIOS/UEFI. The first time Docker Unlocker starts without it, a pop-up explains this and puts the command on the clipboard; its Open PowerShell as administrator button opens an elevated PowerShell to paste it into. If the feature is installed but the hypervisor isn't running, it suggests a restart and the BIOS setting instead.
-
Internet access on the first start (the Alpine image and the Docker packages are downloaded).
Building
Open DockerUnlocker.lpi in Lazarus 4.x (FPC 3.2.2) and press Run, or build from
the command line:
C:/lazarus/lazbuild.exe DockerUnlocker.lpiThe executable is written to bin\DockerUnlocker.exe. The project needs only the
LCL and FPC's own packages; there are no third-party dependencies.
The app icon is DockerUnlocker.ico, built from assets\icon-source.png by
tools\make-icon.ps1 (it cuts out the rounded square with transparent corners
and packs 16-256 px sizes). Lazarus embeds it in the exe; the tray icon draws
its status dot on top of it at run time.
Portable build (bundled QEMU)
One command builds the app and assembles a self-contained folder you can copy anywhere, such as a USB stick or another PC. No QEMU install is needed:
powershell -ExecutionPolicy Bypass -File tools\make-portable.ps1The script:
- builds
DockerUnlocker.exewithlazbuild; - downloads the latest official QEMU for Windows build from https://qemu.weilnetz.de/w64/ and checks its published SHA-512;
- unpacks the installer with 7-Zip instead of running it. If 7-Zip isn't
installed, it fetches a private copy from the official 7-Zip GitHub releases
(checked against GitHub's SHA-256 digests) into
tools\.cache. Nothing is installed on the system; - trims QEMU from ~1.2 GB to ~140 MB. It keeps
qemu-system-x86_64andqemu-img, the DLLs they import (found by reading their PE import tables), and the x86 firmware. It drops the other emulators and tools, the OpenGL/Vulkan DLLs (unused with-display none), the GUI assets and non-x86 firmware; - smoke-tests the bundle (the version, WHPX support compiled in, and a paused boot of the same q35 + virtio machine the app uses);
- writes
dist\DockerUnlocker\(about 170 MB; the zip is smaller).
Options: -QemuVersion 20260811 pins a build, -Zip also writes
dist\DockerUnlocker-portable.zip, -NoPortableData keeps VM data in
%LOCALAPPDATA% instead of next to the exe, and -KeepFullQemu skips the
trimming. Downloads are cached in tools\.cache, so later runs are fast.
The resulting layout:
DockerUnlocker\
DockerUnlocker.exe
portable.txt ??? optional: keep all data in .\data instead of %LOCALAPPDATA%
qemu\
qemu-system-x86_64.exe
qemu-img.exe
*.dll, share\ ... ??? the rest of a QEMU for Windows installQEMU is GPL-2.0 licensed. If you redistribute it, include its license and offer its source code.
Installer
powershell -ExecutionPolicy Bypass -File tools\make-installer.ps1 -Version 1.0.0This writes dist\DockerUnlocker-Setup-1.0.0.exe (about 45 MB), built with
Inno Setup from installer\DockerUnlocker.iss.
If Inno Setup 6 isn't installed, the script downloads the official
innosetup-6.7.3.exe, checks its SHA-256, and installs it for the current user
into tools\.cache. The script stages the app with make-portable.ps1 (without
portable.txt), then compiles the setup.
The installer:
- installs for the current user, with no administrator prompt, to
%LOCALAPPDATA%\Programs\Docker Unlocker: the app, the bundled QEMU and the templates; - adds a Start menu shortcut and, unless unticked, a desktop shortcut, and offers to start Docker Unlocker at the end;
- registers in Settings > Apps with the app's icon;
- on upgrade, asks you to close a running Docker Unlocker and keeps the VM;
- on uninstall, asks whether to also delete the VM and its data
(
%LOCALAPPDATA%\DockerUnlocker). The default is No.
What happens when you press "Start VM"
- First run only: the Alpine Linux cloud image (~180 MB) is downloaded with
the
curl.exethat ships with Windows and checked against its published SHA-512. qemu-imgcreates a copy-on-write system disk on top of that image (the download itself is never modified) and a sparse Docker data disk (64 GB by default; it only uses the space it actually needs).- QEMU boots the VM with WHPX acceleration, no window, and the serial console
written to a file. You can watch it on the VM console page. The VM gets
your real CPU's features (
-cpu host), so images built for x86-64-v2/v3 work. QEMU's defaultqemu64model would reject many of them. Under software emulation the app uses-cpu maxinstead. - First boot only: cloud-init fetches its configuration from a tiny HTTP
server inside Docker Unlocker, then installs Docker, formats and mounts the
data disk, and starts
dockerd. Progress appears at the top of the window. This takes about 5 minutes (measured: 289 s, most of it downloading Docker). - When
GET /_pinganswers, the status turns green and the Containers, Apps, Discover, Images and Terminal pages come alive.
Later starts skip steps 1, 2 and 4 and take about 20 seconds. Stop VM sends Linux an ACPI power-button press and takes about 5 seconds.
Using it
The window has a sidebar on the left (Containers, Apps, Discover, Images,
Terminal, VM console, Log, Settings), the VM's state and Start/Stop at the top,
and DOCKER_HOST plus forwarded ports at the bottom. It follows Windows' dark or
light app mode, and switches live when you change it.
Closing the window keeps Docker Unlocker running in the system tray, along with the VM, your containers and any open terminals. The tray icon's dot shows the VM: green running, amber starting or stopping, red stopped after an error, no dot when stopped. Click the icon to bring the window back; right-click it for Start VM, Stop VM and Exit (Exit asks before shutting a running VM down). Starting Docker Unlocker again while it is in the tray also brings the window back; only one copy runs at a time. Turn this off in Settings ("Closing the window keeps Docker Unlocker running in the tray") to make the close button quit. Windows 11 puts new tray icons in the hidden-icons (^) overflow; drag the icon onto the taskbar to keep it visible.
-
Terminal page: a real terminal inside the app. Pick Linux VM (root shell) or a running container and click Open terminal (or select a container and click Terminal on the Containers page). It is a full xterm-compatible terminal: colors,
top,vi, tab completion and Ctrl+C all work. Each shell gets a tab, and closing a tab ends that shell.- Copy: select with the mouse, then Ctrl+Shift+C or right-click. Paste: Ctrl+Shift+V, Shift+Insert, or right-click with nothing selected.
- Scroll back: mouse wheel or Shift+PgUp / Shift+PgDn.
- The VM shell is root on the Alpine VM itself (
docker,apk,df,dmesg...). It runs through a small privileged helper container,dockerloader-vmshell, which enters the VM's namespaces. - Container shells use
bashif the image has it, otherwisesh. Minimal "distroless" images (Portainer, for example) have no shell at all; the terminal says so.
-
Apps page: one-click installs. Pick an app and click Install. Docker Loader:
- generates its passwords and keys;
- picks a free port;
- starts all the containers it needs (databases included) in the right order;
- opens the app in your browser once it answers;
- shows you its login.
Built-in apps: Invoice Ninja, Nextcloud, Odysseus, Portainer, WordPress, Uptime Kuma, Vaultwarden. Odysseus (PewDiePie's AI workspace) uses the Ollama on your PC for local models: Ollama keeps using your GPU on Windows, and Odysseus reaches it from the VM at
http://10.0.2.2:11434. Open and Remove... manage installed apps. Remove can keep the data, so a reinstall brings back the same database, files and passwords, or it can delete everything. Install from Compose file... deploys anydocker-compose.ymlrecipe you have; files next to it that it refers to as./namecome along. See App templates to add your own apps. -
Discover page: search Docker Hub for an app (for example
uptime kuma), select it and click Install.... Docker Unlocker downloads the image, reads the ports and data folders it declares, and opens an install form with sensible defaults:- one host port per exposed port. Ports below 1024 get 8000 added (80 becomes 8080), and ports that are already taken are skipped;
- a named volume for each data folder, so data survives updates and removing the container;
- start automatically with the VM (restart policy
unless-stopped); - open in the browser when it is ready: the app waits until the page answers, then opens it.
Everything can be edited before installing, including environment variables and a command. Multi-word searches also try the words joined (
invoice ninjafindsinvoiceninja/...). Open on Docker Hub shows the image's documentation page. -
Containers page: start, stop, restart, remove, read logs, open a Terminal, and Open a container's web page. Run... asks for an image name and uses the same install form.
-
Images page: pull, run (with the install form), remove. Free up space... removes every image no container uses (they are downloaded again when needed) and the build cache, then restarts the VM and shrinks its disk file on Windows. Containers, apps and their data are kept. QEMU on Windows can't shrink a disk file while it's in use, so Docker Unlocker trims the freed space inside the VM (
fstrim) and rewrites the file withqemu-img convertbefore the next boot. It first checks there is enough free space for the copy, and only replaces the original once the copy is complete. -
Port forwarding: published ports (
-p 8080:80) are forwarded automatically fromlocalhost:8080on Windows into the VM while the container runs. Turn this off in Settings if you don't want it. -
docker CLI: click
DOCKER_HOST=...at the bottom of the window to copy it, then$env:DOCKER_HOST = "tcp://127.0.0.1:23750" docker ps(Windows
docker.exestatic binaries: https://download.docker.com/win/static/stable/x86_64/.) -
Reset VM... deletes the Linux system disk so the next start provisions a fresh one. You can keep or delete the Docker data disk (images, containers, volumes).
Data lives in %LOCALAPPDATA%\DockerUnlocker, or in data\ next to the exe in
the portable build (images\, vm\, settings.ini, docker-unlocker.log).
The app used to be called Docker Loader: an existing
%LOCALAPPDATA%\DockerLoader folder is still used, and names inside the VM
(dockerloader-apps, dockerloader-vmshell, the dockerloader hostname) are
unchanged so existing VMs and apps keep working.
Troubleshooting
docker-unlocker.login the data folder holds everything the Log page shows for the current session; the previous session is kept asdocker-unlocker.old.log. Include it when reporting a problem.- The VM console page is the VM's serial console: the bootloader, kernel,
OpenRC services and the first-boot provisioning steps (
[dockerloader] ...). - If first-boot provisioning fails, the guest's diagnostics (disk usage and the
last lines of the install log) are copied into the Log page as
guest:lines. The half-provisioned system disk is discarded automatically, so pressing Start VM again retries from scratch. - Reset VM... rebuilds Linux from scratch and can optionally wipe the Docker data disk too.
Security notes
- The Docker API is unauthenticated, as with any local
dockerdexposed over TCP. It is reachable only on127.0.0.1, but any program running on this PC can control Docker, and that means root inside the VM. Don't run Docker Loader on a shared machine you don't trust. - QEMU runs inside a Windows Job Object, so it is killed if Docker Unlocker exits or crashes. A VM can't be left running in the background.
Limitations / roadmap
- No Windows folder sharing yet. Named volumes work, but bind mounts like
-v C:\src:/appdo not, because the VM can't see the Windows filesystem. Options: virtio-9p / virtiofs support in QEMU for Windows, or an SMB mount from the guest. - x86_64 only (arm64 Windows would need an aarch64 image and
-machine virt). - One VM, plain HTTP API on loopback (no TLS).
- Discover installs one container. Apps that need several (a database, for example) need an app template (Apps page) or their own Compose file (Install from Compose file...).
- Apps are reachable on
localhostonly, over plain HTTP. Putting them on your network with HTTPS (a reverse proxy) is not built in.
App templates
An app template is a folder that describes an app with Docker Compose:
templates\invoiceninja\
template.ini name, description, page to open, notes shown after install
compose.yml a normal Docker Compose file
.env settings; Docker Unlocker fills in the generated values
nginx\*.conf any other files the Compose file usesThe shipped templates are in templates\ next to DockerUnlocker.exe. Your own
go in the data folder's templates\ (My templates folder on the Apps page
creates it, with a README); restart Docker Unlocker to load them.
template.ini:
[template]
name=Invoice Ninja
description=Invoices, quotes, payments, expenses and time tracking.
website=https://invoiceninja.com
source=where the recipe comes from
open=http://localhost:${WEB_PORT}/
notes=Log in with ${IN_USER_EMAIL} and password ${IN_PASSWORD}.
download=about 1 GBIn .env, values can use generators, filled in when the app is installed:
| Generator | Produces |
|---|---|
{{password}}, {{password:N}} |
random letters and digits (24 by default) |
{{base64key:N}} |
base64 of N random bytes (Laravel APP_KEY=base64:{{base64key:32}}) |
{{hex:N}} |
N random hex digits |
{{port:N}} |
the first free port from N, for ports: - "${WEB_PORT}:80" |
${OTHER} / ${OTHER:-default} |
an earlier value from the same file |
Random values come from Windows' cryptographic random generator. The filled-in
files are kept in the data folder under apps\<app>\, so the generated
passwords are there if you need them. Reinstalling an app reuses its previous
values, which keeps them matching a database whose data was kept.
How an install runs: Docker Unlocker starts a short-lived helper container from
the official docker:cli image. The helper writes the files into a project
folder inside the VM (on the Docker data disk, in the dockerloader-apps
volume) and runs docker compose up -d there. Its output appears in the
Log page as compose: lines.
Tests
They are plain console programs built against src\:
fpc -Mobjfpc -Sh -Fusrc -FEbin tests\selftest.lprtests\selftest.lprruns offline in seconds. It checks the cloud-init seed (including byte-exact LF delivery), the seed HTTP server, WHPX detection, andTDockerClientagainst a fake Docker API (pull progress and errors, port bindings, log demultiplexing).tests\termtest.lprruns offline: 52 checks of the terminal emulator (cursor movement, wrapping, scroll regions, the alternate screen used byvi/top, 256-color and truecolor SGR, UTF-8, line drawing, scrollback).tests\termlive.lpropens a VM root shell and a container shell against a running VM, runs commands andviin them, and checks the screen.tests\apptest.lprtests app templates against the real VM. It validates every template withdocker compose config, then installs the ones you name, checks their page answers from Windows, and removes them. Withinvoiceninja+reinstallit also logs in with the generated credentials, reinstalls keeping the data, and logs in again. Run it likevmtest:apptest.exe <templates folder> wordpress invoiceninja+reinstall.- For automated GUI tests, setting the environment variable
DOCKERLOADER_NO_BROWSER=1makes Docker Unlocker log the URLs it would open instead of opening a browser. tests\vmtest.lpris the end-to-end test, using the real VM manager and real QEMU. It runs a boot (first-boot provisioning if the VM is fresh), runsnginx:alpinewith-p 18080:80, fetcheshttp://localhost:18080from Windows, shuts down via ACPI, boots again, and checks the container survived. Copyvmtest.exeintodist\DockerUnlockerand run it there.
Source layout
| Unit | Purpose |
|---|---|
uMain |
Main window: sidebar pages for containers, apps, Docker Hub, images, terminals, console, log |
uTheme |
Dark/light colors following Windows, dark native controls and title bar, icon glyphs |
uFlatControls |
Custom-drawn buttons, check box and sidebar items |
uTermScreen |
VT100/xterm terminal emulator (screen, scrollback, escape sequences) |
uTermSession |
Interactive docker exec with a TTY over the Docker API (HTTP upgrade) |
uTermView |
Terminal control: drawing, keyboard, mouse selection, clipboard |
uSettingsForm |
Settings dialog |
uSettings |
Settings (INI) and on-disk layout, QEMU discovery |
uVMManager |
VM lifecycle: download, disks, QEMU launch, boot wait, shutdown, port forwards |
uCloudInit |
cloud-init seed: the first-boot provisioning script |
uSeedServer |
Loopback HTTP server that hands the seed to the guest |
uQMP |
QEMU Machine Protocol client (power-off, port forwards) |
uDockerClient |
Docker Engine REST API client |
uDockerTasks |
Runs slow Docker calls (search, pull, run...) on background threads |
uRunForm |
Install / run dialog, pre-filled from the image's metadata |
uAppDefaults |
Install defaults (container name, free ports, volumes) and Docker Hub search ranking |
uApps |
App templates: loading, filling in .env values, deploying with Docker Compose |
uWHPX |
Detects whether the Windows Hypervisor Platform is installed and running |
uAppIcon |
The app icon at any size, with the tray's VM status dot |
License
Docker Unlocker is released under the MIT License. The installer and
the portable build also include QEMU, which is licensed under the GNU GPL v2; see
THIRD-PARTY-NOTICES.txt and qemu\COPYING in the installed folder.

