Virtual Machines
miniblue emulates Azure Virtual Machines (Microsoft.Compute/virtualMachines) via ARM endpoints. When Docker is available on the host, each VM is backed by a real Docker container on its own per-VM network. Without Docker the lifecycle metadata still works, and any operation that needs a running container returns 409 DockerUnavailable.
A VM is more than a single container. You can deploy named service containers onto it, stream their logs, run commands inside it, open an interactive shell and attach managed identities for token attestation.
Docker detection
miniblue probes for the docker CLI at startup and runs docker info. The log shows:
or it falls back to stub mode when the CLI is absent or the daemon is unreachable.
Image mapping
VMs boot a Linux container image. miniblue resolves the image in this order:
properties.miniblue.imageis an explicit container image and the recommended escape hatch (for examplenginx:alpine).properties.storageProfile.imageReferenceis mapped through an alias table.- With no image reference at all, the default is
ubuntu:24.04.
imageReference contains |
Container image |
|---|---|
24.04 / 2404 / 24_04 |
ubuntu:24.04 |
22.04 / 2204 / 22_04 |
ubuntu:22.04 |
ubuntu (any other) |
ubuntu:24.04 |
debian |
debian:stable-slim |
An imageReference that is present but cannot be mapped fails loudly, so a typo does not silently boot the wrong OS. Set properties.miniblue.image to bypass the table.
API endpoints
| Method | Path | Description |
|---|---|---|
PUT |
.../Microsoft.Compute/virtualMachines/{name} |
Create or update a VM |
GET |
.../Microsoft.Compute/virtualMachines/{name} |
Get a VM (live power state) |
DELETE |
.../Microsoft.Compute/virtualMachines/{name} |
Delete a VM, its services and network |
GET |
.../Microsoft.Compute/virtualMachines |
List VMs in a resource group |
GET |
/subscriptions/{sub}/providers/Microsoft.Compute/virtualMachines |
List VMs across the subscription |
POST |
.../virtualMachines/{name}/start |
Start the VM and its services |
POST |
.../virtualMachines/{name}/powerOff |
Stop the VM and its services |
POST |
.../virtualMachines/{name}/restart |
Restart the VM and its services |
POST |
.../virtualMachines/{name}/runCommand |
Run a command inside the VM |
GET |
.../virtualMachines/{name}/logs |
Read service logs (?service=, ?tail=N, ?follow=true) |
GET |
.../virtualMachines/{name}/services |
List deployed services |
PUT |
.../virtualMachines/{name}/services/{service} |
Deploy or replace a service |
GET |
.../virtualMachines/{name}/services/{service} |
Get a service |
DELETE |
.../virtualMachines/{name}/services/{service} |
Remove a service |
Paths are abbreviated; the full prefix is /subscriptions/{sub}/resourceGroups/{rg}/providers.
Create a VM
azlocal group create --name myRG --location eastus
azlocal vm create --resource-group myRG --name web01 --image ubuntu:24.04
or via the ARM API:
curl -X PUT "http://localhost:4566/subscriptions/sub1/resourceGroups/myRG/providers/Microsoft.Compute/virtualMachines/web01" \
-H "Content-Type: application/json" \
-d '{
"location": "eastus",
"properties": {
"storageProfile": {
"imageReference": { "publisher": "Canonical", "offer": "ubuntu", "sku": "24_04-lts" }
}
}
}'
Response (201 Created) follows the ARM envelope with provisioningState: Succeeded and a miniblue.powerState field. When Docker is available the create starts a keep-alive container on a per-VM network. A VM whose container fails to start fails fast with 409 ContainerStartFailed and persists nothing, unlike ACI which falls back to a stub.
Power actions
azlocal vm stop --resource-group myRG --name web01
azlocal vm start --resource-group myRG --name web01
azlocal vm restart --resource-group myRG --name web01
Power actions cascade to every service container on the VM. powerState on a GET is refreshed live from docker inspect.
Deploy services
A service is a named container that runs on the VM's network with host-published ports.
azlocal vm deploy --resource-group myRG --name web01 \
--service api --image nginx:alpine --ports 8080:80 \
--env LOG_LEVEL=debug --env REGION=local
Deploying a service whose host port clashes with a sibling returns 409 PortConflict. Re-deploying the same service name replaces the existing container. List and remove services with:
azlocal vm services --resource-group myRG --name web01
azlocal vm service-delete --resource-group myRG --name web01 --service api
Logs
# combined, labelled view of every service
azlocal vm logs --resource-group myRG --name web01
# a single service, last 100 lines
azlocal vm logs --resource-group myRG --name web01 --service api --tail 100
# live stream
azlocal vm logs --resource-group myRG --name web01 --follow
Combined output prefixes each line with [service]. Streaming uses chunked text/plain and ends cleanly when the client disconnects.
Run commands and SSH
# one-off command, mirrors Azure runCommand
azlocal vm run-command --resource-group myRG --name web01 --command "uname -a"
# interactive shell (docker exec -it, /bin/bash with /bin/sh fallback)
azlocal vm ssh --resource-group myRG --name web01
runCommand captures stdout, stderr and the exit code. Both require a running container and return 409 VMNotRunning or 409 DockerUnavailable otherwise. vm ssh shells out to docker exec directly, so it needs Docker on the same host as the CLI.
Managed identity attestation
User-assigned managed identities live under Microsoft.ManagedIdentity/userAssignedIdentities and can be attached to a VM. A workload inside the VM (or its services) then obtains a token with no code changes through the standard IDENTITY_ENDPOINT / IDENTITY_HEADER protocol that Azure SDK credential chains already probe.
azlocal identity create --resource-group myRG --name app-id
azlocal vm identity-assign --resource-group myRG --name web01 --identity app-id
# from inside the VM, the SDK or a curl call hits the token endpoint
curl "http://localhost:4566/metadata/identity/oauth2/token?resource=https://management.azure.com/"
Tokens carry xms_mirid and VM claims and are verifiable via POST /metadata/identity/introspect. The signature is a clear mock (alg: none); validation is by store lookup, not by trusting token contents. Remove an assignment with azlocal vm identity-remove.
azlocal command reference
azlocal vm create|list|show|delete
azlocal vm start|stop|restart
azlocal vm deploy|services|service-delete
azlocal vm logs [--service NAME] [--tail N] [--follow]
azlocal vm run-command --command "..."
azlocal vm ssh
azlocal vm identity-assign|identity-remove --identity NAME
azlocal identity create|list|show|delete
Limitations
- The VM is a Linux container, not a real virtual machine. There is no kernel isolation, no Windows images and no cloud-init.
- The keep-alive container runs
sleep; provisioning extensions, custom-data scripts and boot diagnostics are not emulated. - Without Docker, lifecycle metadata works but every runtime operation (power, deploy, logs, run-command, ssh) returns
409 DockerUnavailable. - Container and network names are derived from the resource group and VM name, so those must be valid Docker identifiers.