Skip to main content

Docker in Docker on Woodpecker (Running in Docker) for Blog Image Publishing

· 4 min read
Mark Burton
Software Engineer & Technical Writer

I wanted my self-hosted Woodpecker instance (itself running in Docker) to build this blog into a Docker image and push it to Docker Hub, so my web server can just pull and run it.

This post is a practical working note while I set it up and validate each piece.

The Core Question​

If Woodpecker is running in a container, how can pipeline steps run docker build and docker push?

In practice, there are two common ways:

  1. Mount the host Docker socket into the Woodpecker runner/agent.
  2. Run a Docker-in-Docker (DinD) daemon and point the pipeline at it.

For homelab simplicity, I am starting with option 1 first, then only moving to full DinD if I need stronger isolation.

Quick Recommendation​

Start with host socket mounting first.

It is simpler, faster, and easier to debug. It does mean pipeline jobs can effectively control the host Docker daemon, so treat it as trusted CI infrastructure.

If you need better isolation boundaries later, move to DinD or rootless BuildKit.

Architecture I Am Targeting​

Git push
-> Woodpecker server
-> Woodpecker agent (container)
-> Docker daemon (host socket or DinD)
-> Build blog image
-> Push to Docker Hub
-> Web server pulls latest image

Prerequisites​

  • A working Woodpecker server and agent in Docker.
  • Docker Hub account with a repository (for example yourname/myblog).
  • Woodpecker secrets for:
    • docker_username
    • docker_password
    • docker_repo
  • Existing Dockerfile that builds the blog runtime image.

1. Mount the Docker socket into the Woodpecker agent​

In the container that executes Docker commands, mount:

volumes:
- /var/run/docker.sock:/var/run/docker.sock

If you run server and agent separately, this is usually needed on the agent.

2. Ensure the step image has Docker CLI​

Use an image with Docker CLI available, for example docker:24 (or similar).

3. Build and push in Woodpecker​

Example pipeline step:

steps:
- name: docker-build-and-push
image: docker:24
environment:
DOCKER_USERNAME:
from_secret: docker_username
DOCKER_PASSWORD:
from_secret: docker_password
DOCKER_REPO:
from_secret: docker_repo
commands:
- echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
- docker build -t "$DOCKER_REPO:latest" .
- docker push "$DOCKER_REPO:latest"
docker build -t "$DOCKER_REPO:${CI_COMMIT_SHA}" -t "$DOCKER_REPO:latest" .
docker push "$DOCKER_REPO:${CI_COMMIT_SHA}"
docker push "$DOCKER_REPO:latest"

This gives immutable rollback points.

Option 2: True Docker-in-Docker (DinD)​

If you want the pipeline to avoid using the host daemon directly, run a DinD service and point DOCKER_HOST at it.

High-level shape:

steps:
- name: docker-build-and-push-dind
image: docker:24
environment:
DOCKER_HOST: tcp://docker:2375
commands:
- docker version
- docker build -t "$DOCKER_REPO:latest" .

And a DinD service container (for example docker:dind) with privileged mode.

Important: DinD introduces extra complexity (networking, TLS decisions, daemon lifecycle, layer caching). That is why I am using socket-mount first.

Security Notes (Important)​

Granting a CI job access to /var/run/docker.sock is effectively root-equivalent access on that Docker host.

Mitigations I am applying:

  • Keep Woodpecker runner on a trusted internal host.
  • Restrict who can trigger pipelines.
  • Use separate host(s) for CI and production workloads if possible.
  • Store Docker Hub credentials in Woodpecker secrets only.
  • Use scoped Docker Hub tokens instead of account password.

Minimal Rollout Plan​

  1. Confirm Woodpecker can run a simple pipeline step (echo, node -v).
  2. Add socket mount to agent.
  3. Add docker version command in pipeline to verify daemon access.
  4. Build local image in pipeline (no push yet).
  5. Add Docker Hub login + push.
  6. Update web server deployment to pull and run latest image.
  7. Add commit-SHA tagging and a rollback script.

Troubleshooting Checklist​

If docker fails in pipeline:

  • docker: not found

    • Use a step image that includes Docker CLI (docker:24).
  • Cannot connect to the Docker daemon

    • Confirm socket mount exists in the container running the step.
    • Check permissions on /var/run/docker.sock.
  • unauthorised: incorrect username or password

    • Recreate Woodpecker secrets.
    • Use Docker Hub access token, not your account password.
  • Build works locally but fails in CI

    • Check build context paths.
    • Ensure blog build artefacts are present before docker build.

How This Maps To My Repo​

I already have:

  • Dockerfile at repository root
  • Build and package script at scripts/build-and-package.ps1
  • Docker notes in DOCKER.md
  • Woodpecker pipeline file at .woodpecker/woodpecker.yml

Next, I need to simplify and finalise the pipeline so there is one clean image build/push path.

Draft Notes To Refine Later​

  • Confirm exact Woodpecker agent/server compose used in homelab.
  • Capture final working docker-compose.yml snippet.
  • Capture final working .woodpecker/woodpecker.yml snippet.
  • Add a section on image retention and cleanup.
  • Add deployment script from web server side (docker pull + restart).

When I finish testing, I will replace this draft checklist with the exact working configuration I ended up with.