Forgejo Runner¶
Docker in docker¶
Docker in Docker (dind) is a configuration where a docker daemon runs inside a container that is itself managed by a Docker instance.
Technically, this is achieved by running a container using the docker:dind image.
[!CAUTION] Docker-in-docker introduces several criticial security risks because it requires the container to run in privileged mode (
--privileged). The--privilegedflag grats the container nearly all the capabilities of the host machine"By default, Docker containers are unprivileged and cannot, for example, run a Docker daemon inside a Docker container" https://docs.docker.com/engine/containers/run/#runtime-privilege-and-linux-capabilities
Resources - https://learn.snyk.io/lesson/container-runs-in-privileged-mode
In the current setup, Docker in Docker is integrated with a forgejo runner, which itself runs as a container as well. Forgejo runner need access to a docker daemon (aka dind) to run other images.
The forgejo-runner runs a container that is configured with the DOCKER_HOST environment variable pointing to the dind sidecar.
# docker-compose.yml
services:
dind:
image: "docker:29.3.0-dind"
privileged: true
command: ['dockerd', -'H', 'tcp://0.0.0.0:2375']
forgejo-runner:
image: "code.forgejo.org/forgejo/runner"
environment:
DOCKER_HOST: tcp://dind:2375
Dind custom configuration
Having an internal domain served by an internal dns server and having self-signed certificates bring a lot of issues. Creating a multilayer approach where a docker daemon runs as a container within docker on a virtual machine adds more complexity.
For example, some containers may have their own /etc/resolv.conf or docker may update it pointing out to external servers (e.g 1.1.1.1, 8.8.8.8).
These external servers do not resolve internal domains.
With this set up, I need to take into account the following considerations:
- The dind container has acccess (--privileged) to the host kernel (the virtual machine)
- dind maintains its own /var/lib/docker. Be aware of replicated storage (persistent volumes and images)
- By default, containers spawned inside dind might bypass some host-level network restrictions
- Image layers may be epheral, they may not be cached.
The main dockerd is running as rootless (using docker user). This adds a layer of security, protecting the host's root user.
I had to set up the following configuration in dind:
- (Pre): Set up self signed CA in virtual machine
- /usr/local/share/ca-certificates/homelab-ca.crt
- Run update-ca-certificates
services:
dind:
volumes:
- "./dind-config/daemon.json:/etc/docker/daemon.json:ro"
- "/etc/ssl/certs:/etc/ssl/certs:ro"
- "/run/systemd/resolve/resolv.conf:/etc/resolv.conf:ro"
which dind-config/daemon.json:
{
"insecure-registries": ["forgejo.internal"], // It didn't work to me
"tls": false,
"dns": [<INTERNAL_IP>]
}
insecure-registriestells docker to trust the specified domains. However it did not work to me<INTERNAL_IPis the IP of the dns resolver (e.g dns server, router server, etc)"tls": falseis used to simplify the connection between runner and daemon in this local "controlled" environent
Using self hosted images
A forgejo job runs in its own docker network isolated for that pipeline. If the forgejo action requires a self hosted image,
forgejo runner is not able to resolve internal domains, then docker pull would fail.
Found a fix that actually works without breaking everything: just expose the dind port in docker-compose and set DOCKER_HOST: tcp://172.17.0.1:2375 in the Forgejo step's env
For example:
services:
dind:
volumes:
- "/etc/ssl/certs:/etc/ssl/certs:ro"
ports:
- "172.17.0.1:2375:2375"
172.17.0.1 is the internal network endpoint (docker0 bridge) created by dind. It is usually the Gateway IP of docker
It is also required to mount the host certificate directory ("/etc/ssl/certs:/etc/ssl/certs:ro") since docker pull ignores linux certificates (/etc/ssl/certs).
I had to copy /usr/local/share/ca-certificates/homelab-ca.crt to /etc/docker/certs.d/forgejo.internal/ca.crt
Forgejo Actions¶
- workflow > job > step
GITHUB_ENVvsGITHUB_OUTPUTecho "MY_TOKEN=$TOKEN" >> $GITHUB_ENV- Mask secrets in logs:
echo ::add-mask::$TOKEN