# How do I isolate self-hosted CI/CD runners and build an ephemeral, clean-after-each-run environment?

> Move the runner to a register-run-once-destroy model and run jobs unprivileged; never hand over the Docker socket, and never run fork PRs on it.

- Asked: 2026-06-12
- Answered: 2026-06-12
- Asked by: Cüneyt
- Tags: ci-cd, guvenlik, docker
- Source: https://www.muhammetsafak.com.tr/en/just-ask/isolating-and-securing-self-hosted-ci-cd-runners/
- Language: en-US
- Author: Muhammet Şafak

---
**Question:** Due to our source-code security policy we can't use cloud runners (GitHub Hosted); we run Self-Hosted Runners on our own VPSes. But a test or a buggy script can damage the server's root dir (`rm -rf /`) or take over the box by manipulating the Docker socket (`/var/run/docker.sock`).

How do I architect full isolation and an environment that's wiped after each pipeline (ephemeral) on a self-hosted runner?


Short answer: a persistent self-hosted runner that runs arbitrary code on a box with root and the real Docker socket is a server takeover waiting to happen. Build on two principles: **ephemerality** and **isolation**.

The root of the risk: you're running code you don't trust on a fully-privileged, persistent machine.

1. **Ephemerality: each job runs in a fresh, throwaway environment.** Switch the runner to a "register → run one job → deregister" model; each job runs in a single-use VM or container that's **destroyed** when it finishes. Nothing persists between jobs, so a poisoned workspace can't affect the next job.
2. **Isolation: don't give the runner host root or the real Docker socket.** Run jobs in **unprivileged** containers (or rootless Docker / a VM per job); drop capabilities, never use `--privileged`, and mount nothing sensitive. Handing over `/var/run/docker.sock` is equivalent to handing over root.
3. **Never run untrusted fork PRs on a self-hosted runner.** A PR from a fork means arbitrary code on your infrastructure; put these behind required approval. This is the most overlooked and most dangerous vector.
4. **Network-segment runners away from prod.** Even if a job escapes, it shouldn't reach prod resources. On tooling: ephemeral runners via Actions Runner Controller on Kubernetes, or autoscaling VMs recreated for each job.

**Bottom line:** I'd set up ephemeral one-job runners + unprivileged isolation + a fork-PR ban + network segmentation, and I'd **treat every job as hostile**. The power of a self-hosted runner is control, but the cost is responsibility — I explain how container isolation works and why handing over the socket is handing over root, from the ground up, in the hub Docker post.

## Related Reading

- [Docker containerization: installation and basic usage](/en/blog/docker-containerization-installation-and-basic-usage/) — Blog
- [My non-root container can't write to the mounted storage directory—how do I fix the permissions?](https://www.muhammetsafak.com.tr/en/just-ask/my-non-root-container-cant-write-to-the-mounted-storage-directory/) — Just Ask
- [How do I set up secretless AWS access in CI/CD with OIDC and IAM roles?](https://www.muhammetsafak.com.tr/en/just-ask/secretless-ci-cd-with-aws-iam-roles-and-oidc/) — Just Ask
- [How do I shrink Docker images with multi-stage builds and distroless?](https://www.muhammetsafak.com.tr/en/just-ask/shrinking-docker-images-with-multi-stage-builds-and-distroless/) — Just Ask
