Title: GitHub Actions Docker in Docker on a self-hosted runner
URL: https://codemany.com/articles/github-actions-docker-in-docker
Description: Run Docker in Docker in GitHub Actions: when you need it, the risks, and how to build images on a self-hosted runner without sharing the Docker socket.
All pages as Markdown: https://codemany.com/llms.txt

---

[Home](https://codemany.com/) › [Articles](https://codemany.com/articles)

# Docker in Docker in GitHub Actions

GitHub Actions Docker in Docker means a job that runs inside a container still needs Docker to build or run other containers. On a self-hosted runner, the safe way to do it is to give each job its own Docker, not your machine's shared Docker socket.

## What Docker in Docker means

Docker runs as a daemon, a program in the background. The `docker` command talks to it over a socket, a file at `/var/run/docker.sock`.

"Docker in Docker" is used for two setups:

- **True Docker in Docker.** A second daemon runs inside a container, often the `docker:dind` image. It needs `--privileged`.
- **Docker out of Docker.** A container mounts the host's socket. Its `docker` command then drives the host's daemon.

Both let a job run `docker build` from inside a container. Both give that job a lot of power.

## When a job needs it

Most jobs don't. If your job runs right on the runner, it can call Docker directly. You need one of the setups above when:

- the job itself runs in a container (`container:` in your workflow) and must build an image;
- a test suite starts its own containers, like Testcontainers;
- your runners run inside Kubernetes pods.

GitHub's Ubuntu runners come with Docker. GitHub's docs say its Apple silicon macOS runners don't support nested virtualization, which Docker on a Mac needs for its Linux VM.

If you control the runner, first ask if you can skip the container. Run the job on the runner itself, and call Docker from there. That is simpler, and it often removes the need for Docker in Docker at all.

## Why the raw Docker socket is risky

Access to the Docker socket is root on the machine. A job that can talk to it can start a privileged container and mount `/`. Then it can read or change any file.

On a GitHub-hosted runner, that is fine. The machine is thrown away after the job. On your own GitHub self-hosted runner with Docker, the machine stays. A bad job could leave something behind for the next one.

So only share the raw socket with code you trust. Be careful with public repos, where anyone can open a pull request. Read [runner security](https://codemany.com/docs/guides/runner-security) first.

## Docker in a fresh VM per job on Linux

The cleanest answer is a fresh machine per job, like GitHub does. Codemany can do that on Linux.

By default, a Linux machine runs jobs in host mode. Each job gets a fresh, single-use runner, and uses the machine's Docker if Docker is installed. Jobs from public repos run as separate users. They reach Docker through a filtered socket that blocks privileged containers, host mounts outside their home and other jobs' containers.

You can turn on VM mode for full isolation. Each job then gets its own KVM virtual machine with Docker and passwordless sudo. The VM is thrown away after the job. It is slower, since each job boots a VM first, and it needs KVM.

```
ls /dev/kvm                    # KVM must be there
codemany isolation vm-image    # build the VM image once
codemany isolation vm          # turn on a VM per job
codemany isolation host        # switch back any time
```

In VM mode, every Linux runner carries the `docker` label. More in [a VM per job](https://codemany.com/features/vm-per-job) and [Linux runners](https://codemany.com/features/linux-runners).

Pick the mode per machine. Use host mode for your own private repos, where speed and warm caches matter. Use VM mode where you want each job sealed off from the machine.

## Docker on a Mac runner

On a Mac, Docker runs inside a small Linux VM, from Docker Desktop or Colima. Codemany can install Colima for you, as your user, never root.

The runner gets a `docker` label only while a Docker engine answers. Jobs that need Docker should ask for it, so they wait for a ready runner. Turn on **Keep Docker running** in Settings › This Mac › Tools to keep the label steady.

Mac jobs run as your user, with your files. A fresh runner keeps jobs apart from each other, not from your Mac. So run only code you trust there. See [security and privacy](https://codemany.com/docs/security).

## Example: build and push an image

This is a common docker build GitHub Actions job. It builds an image with Buildx and pushes it to GitHub's container registry. It runs on your own Linux machine.

```
name: Image
on:
  push:
    branches: [main]

jobs:
  image:
    runs-on: [self-hosted, Linux, codemany]
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4
      - uses: docker/setup-buildx-action@v3
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ghcr.io/my-org/my-app:latest
```

Change `my-org/my-app` to your own name, in lowercase. On a Mac, use `runs-on: [self-hosted, macOS, docker, codemany]`. To make builds faster, read [Docker layer caching in GitHub Actions](https://codemany.com/articles/docker-layer-caching-github-actions).

## Questions

### Do GitHub-hosted runners have Docker?

Ubuntu ones do. GitHub's Apple silicon macOS runners don't support nested virtualization, which Docker on a Mac needs.

### Is mounting /var/run/docker.sock safe?

It gives the job root on the machine, so only do it for code you trust.

### Can Codemany jobs use Docker?

Yes. On Linux, the opt-in VM mode gives each job its own Docker and sudo. In the default host mode, jobs use the machine's Docker if it is installed.

## Related

Read next: [a VM per job](https://codemany.com/features/vm-per-job), [Linux runners](https://codemany.com/features/linux-runners), [runner security](https://codemany.com/docs/guides/runner-security), [Docker layer caching](https://codemany.com/articles/docker-layer-caching-github-actions) and [security and privacy](https://codemany.com/docs/security).

[Start a 14-day trial](https://codemany.com/docs/get-started) No card needed. $20 a month for 2 machines after that.
