Skip to content

Will my image run?

Updated

Most images that work under Docker work as an AppVM unchanged. This page is the check to run before you import one, and the explanation when something exits straight away.

  • Service images such as nginx, redis, postgres, or your own service: import and run. Remember to attach a volume for anything that must persist.
  • Base and runtime images such as alpine, debian, ubuntu, node, or python: these exit immediately. Their entrypoint is an interactive shell or REPL with nothing attached to it. Turn on keepalive if you want to stay inside and poke around, or use an image with a real entrypoint to run a service.
  • Non-root images run fine. To write to a data volume, the image must already contain that directory owned by the image’s user.
  • Distroless and other images with no shell: the workload runs. What you lose is everything that needs /bin/sh, which means no web terminal, no keepalive, no shell-form entrypoint, and no shell-form health check. Reading and writing the machine’s files, and starting a program by name, do not need a shell, and neither does a TCP or HTTP health probe.
  • An image whose entrypoint is an init system (systemd, /sbin/init) is refused at import with the code AC-INIT. See Init images and container runtimes.
  • A container runtime as the workload (containerd, podman, k3s): the guest mounts cgroup2 and offers the namespace abilities a runtime checks for before it starts. What has been exercised is spelled out in Init images and container runtimes.
Image Runs What to know
Alpine and derivatives Yes A bare alpine entrypoint is a shell, so it needs keepalive
Debian / Ubuntu Yes Bare images exit immediately and need keepalive; service images are fine. The image’s own /etc/hosts is empty; the guest writes one at every boot
nginx Yes Runs unchanged. Its declared stop signal is honoured, so shutdown drains gracefully
PostgreSQL Yes, with care See the note below about PGDATA
Redis Yes Attach a volume at /data for persistence. A very large save may not finish inside the stop grace period
Node / Python Yes Service images are fine; a bare node or python3 entrypoint is a REPL and exits
Go distroless Yes, with limits Runs, including a numeric user, but has no shell
Images with a health check Yes Probing and status work. Automatic recovery follows the restart policy, which starts at no
Images that fork children Yes Child processes are reaped and signals reach the group
Images that ignore SIGTERM Yes Escalates to a forced stop after the grace period
An image whose entrypoint is systemd or /sbin/init No Refused at import with AC-INIT. Lightweight inits such as tini and dumb-init, and process managers such as s6 and supervisord, are accepted. See Init images and container runtimes
A container runtime as the workload Yes containerd, podman, and k3s look for the cgroup mount the guest provides; k3s runs with its image’s own command

A brand new volume takes its owner and mode from the directory the image ships at that mount point. If the image does not contain the directory, the mount point is created owned by root, and a non-root workload will not be able to write to its own volume.

Two ways out: use an image that ships the directory with the right owner, or use an image whose entrypoint starts as root and adjusts ownership itself, which is what the postgres and redis images do.

An AppVM is one image with one main process, and it is not a container engine: Virtainer Free does not schedule several containers inside one machine, and an image’s own processes share that machine’s single filesystem and network. What the guest does provide is a normal Linux kernel with the things a container runtime asks for before it starts.

An init system cannot be the entrypoint. The guest runs the image’s process as a supervised child of its own init, not as PID 1, so a systemd or /sbin/init entrypoint cannot boot as it would on a machine. Virtainer Free refuses such an image at import with AC-INIT rather than letting it become a machine that starts and stops at once. Import a service or application image instead. tini, dumb-init, s6 and supervisord run as ordinary foreground processes and are accepted.

The guest mounts a unified cgroup hierarchy. Container runtimes look for that path before they do anything else, so /sys/fs/cgroup is populated at boot.

Templates carry no build-time /run. The host clears /run when it converts an image, because its contents are runtime mounts rather than image content and /run is a fresh tmpfs at every boot. A template converted earlier holds what it was given: re-import or rebuild it to pick up the clean /run, then redeploy the machines that use it.

What has been exercised on a real host:

  • A single-node k3s reaches Ready using the image’s own command, with no wrapper entrypoint, and a pod on the flannel network produces logs through kubectl.
  • A four-node Kubernetes cluster built with kubeadm, on Ubuntu 24.04 images with one node per AppVM, reaches Ready on all four nodes.

Something to expect if you run a container runtime as the workload:

  • A container runtime pulls the images it needs over the network, from inside the machine, so it depends on that machine’s own network and egress rather than on what this host already imported.
  • Automatic anonymous volumes. A VOLUME declaration does not create a volume for you. Persistent data needs an explicitly attached volume. The create form does warn when the image declares a path as persistent and you have not attached anything to it.

  • Images built for a platform other than linux/amd64. Admission names the platform it found and says which one it needs.

  • Health checks declared by OCI-format images. The format does not carry the field. You can add one yourself when you create the machine.

  • Interactive stdin and TTY entrypoints. Keepalive is an escape hatch for debugging, not a way to run an interactive program as a service.

  • Several containers as several units in one machine. One image is one machine, with one main process that owns its health and its result. Sidecars you declare are extra processes inside that one environment, not separate containers with their own disk, network, or restart policy. Running a container runtime as your workload is a different thing, and it works.

    A machine takes up to eight sidecars. Each needs a name of 1 to 32 lowercase letters, digits or hyphens, starting and ending with a letter or digit, and main is reserved for the workload itself. Each declares its own command; it inherits the image’s environment, working directory and user, so a redeploy onto a newer build re-inherits those from the new image. An older guest does not understand named sidecars, and the machine does not quietly start without them: it fails to boot with FATAL [capability] guest did not acknowledge named sidecars within 10 seconds. Redeploy or reboot it to bring the guest up to date.

  • Virtual machines inside an AppVM. The guest has no /dev/kvm, which is deliberate: a container runtime inside a machine is supported, a hypervisor inside it is not.

If the image declares a stop signal, that is what gets sent, so postgres gets its fast-shutdown signal and nginx gets its graceful-drain signal. The grace period defaults to 10 seconds and can be raised to 300 when you create the machine. After it expires the workload is killed and the machine is torn down.