# How to run docker \*inside\* a sprite

**URL:** <https://community.fly.io/t/how-to-run-docker-inside-a-sprite/28995>\
**Category:** General\
**Created:** [November 4, 2026, 10:10am UTC](https://community.fly.io/t/how-to-run-docker-inside-a-sprite/28995 "2026-11-04T10:10:43Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![DoogieMuc](https://sea1.discourse-cdn.com/fly1/user_avatar/community.fly.io/doogiemuc/32/51367_2.png) [@DoogieMuc](https://community.fly.io/u/DoogieMuc)\
**Post date:** [November 4, 2026, 10:10am UTC](https://community.fly.io/t/how-to-run-docker-inside-a-sprite/28995/1 "2026-11-04T10:10:43Z")

</div>

This post is a follow up to the great thread: [How to get Docker running on Sprites?](https://community.fly.io/t/how-to-get-docker-running-on-sprites/27168)

**Disclaimer:** I do understand the post below. I checked it. But (obviously 🙂 I didn’t write it. It’s written by Claude-AI. But we also tested this setup. I _am_ using this as we speak.

Remark by the “human-in-the-loop” old-school dev: Yes this feels like docker inside docker inside docker inside an unbelievable awesome genius [fly io custom docker without docker](https://fly.io/blog/design-and-implementation/) in side a docker … 🙂

So yes it’s maybe an unusual, complex setup. But some people might want to run their “golden-tarball” (how a docker-image was called 20 years ago) before going to PROD…

* * *

## Running containers (not just `docker ps`) inside a Sprite

Installing Docker and starting `dockerd` by hand works, as shown above. But on my Sprite (Ubuntu 26.04, kernel 6.12-fly),  
the first `docker run` failed in two ways:

1. **cgroups:** `runc create failed: unable to apply cgroup configuration: cannot enter cgroupv2 "/sys/fs/cgroup/docker" with domain controllers -- it is in an invalid state`. Inside the Sprite, `/sys/fs/cgroup` is a namespaced  
_threaded_ cgroup that already contains the Sprite’s own processes, so no child cgroup can get the memory/io  
controllers. Moving processes into a leaf cgroup (the usual docker-in-docker trick) fails with  
`Operation not supported`.
2. **Storage:** `dmesg` shows `overlayfs: maximum fs stacking depth exceeded`. The Sprite’s `/` is already an overlay.  
Docker 29 defaults to the containerd image store, which lives in `/var/lib/containerd` on that overlay.

What fixes both:

- Sprites already mount a **dedicated ext4 volume at `/var/lib/docker`** (check with `findmnt /var/lib/docker`).  
Use the classic `overlay2` driver so everything lives on that volume.
- Run containers with **crun with its cgroup manager disabled**.

### Setup

```bash
sudo apt-get update
sudo apt-get install -y docker.io docker-compose-v2 crun
sudo usermod -aG docker "$(whoami)" # $USER is empty in a Sprite shell

```

`/etc/docker/daemon.json`:

```json
{
  "features": { "containerd-snapshotter": false },
  "storage-driver": "overlay2",
  "default-runtime": "crun-nocg",
  "runtimes": { "crun-nocg": { "path": "/usr/bin/crun", "runtimeArgs": ["--cgroup-manager=disabled"] } },
  "bridge": "none",
  "iptables": false,
  "ip6tables": false,
  "live-restore": true,
  "log-driver": "local",
  "log-opts": { "max-size": "10m", "max-file": "3" }
}

```

`bridge: none` and `iptables: false` are optional: I only use `--network host`, so Docker doesn’t need to touch  
iptables or the forwarding sysctl (which is off on a Sprite).

There’s no systemd, so run `dockerd` as a Sprite service. It then starts at boot and restarts if it crashes:

```bash
sprite-env services create docker --cmd sudo --args /usr/bin/dockerd
sudo docker info | grep -E 'Storage Driver|Backing Filesystem|Default Runtime'
# Storage Driver: overlay2 / Backing Filesystem: extfs / Default Runtime: crun-nocg

```

If a web server in front needs the containers, let it depend on that service, e.g.  
`sprite-env services create web --cmd /usr/sbin/nginx --needs docker --http-port 8080`.

### Test

```bash
sudo docker run --rm --network host hello-world

```

A locked-down container works too: `--read-only --tmpfs /tmp --cap-drop ALL --security-opt no-new-privileges:true --user 1001:1001`. Inside it, `/proc/self/status` shows `CapEff: 0`, `NoNewPrivs: 1`, `Seccomp: 2`. Outbound IPv6  
works from a `--network host` container. `--restart unless-stopped` works too: I killed the container’s process and  
Docker started it again.

### Caveats

- **No resource limits.** With cgroups disabled, `--memory` / `--pids-limit` are accepted but **not enforced**.  
Limit the app itself, e.g. a fixed `-Xmx` for the JVM.
- **Checkpoints** only capture the Sprite’s writable overlay. `/var/lib/docker` is a separate volume, so don’t rely  
on a checkpoint restore to roll back images; pull them again.
- `--network host` only. Without a bridge, there’s no port publishing (`-p`).
- **Cold wake:** [result pending]
- Docker Hub’s anonymous pull limits apply as usual.

Tested 2026-11-04 with Docker 29.1.3 (Ubuntu `docker.io`), containerd 2.2.2 and crun 1.21.
