Fix Docker x509 Errors in GitHub Actions CI
Zusammenfassung
Pixel shares a guide to Docker x509 certificate errors in GitHub Actions when a registry uses a private CA. It explains why the daemon rejects the certificate, where to install the CA, and why marking a registry insecure is an unsafe workaround.
DEV Community
Docker x509 certificate signed by unknown authority in CI
Docker x509 certificate signed by unknown authority in GitHub Actions means the daemon reached your registry, read the certificate it presented, and found no trusted root behind it, so to fix registry TLS you install that CA where Docker looks for it. Marking the registry insecure also silences the error, and it turns off the check that was protecting every layer the job pulls. Three places hold a CA, and they serve different clients. Installing one and testing with another is why this error survives the first fix. What this error means Public images pull normally and one host fails, every time, at the first request rather than part way through a download. Two wordings are in circulation: the older one ends the Get line with "x509: certificate signed by unknown authority", and newer clients put "tls: failed to verify certificate" in front of it, which is the shape cnoe-io/idpbuilder#520 reports. The failing URL is usually the registry /v2/ endpoint, or something under it, because verification happens during the handshake and the first request is the one that trips it. We did not reproduce this on a runner: it needs a registry holding a certificate from a private CA, which we are not going to stand up on the public internet, so the log below is the shape a private registry prints rather than a recorded run. ```The shape a private registry prints (illustrative, not a recorded run) Error response from daemon: Get "https://registry.internal.example.com:5000/v2/": tls: failed to verify certificate: x509: certificate signed by unknown authority
Common causes
The registry certificate comes from a private CA
The ordinary case. An internal registry, a corporate proxy that re-signs TLS, or a self-signed certificate on a test registry all present a chain that ends in a root the runner has never seen. Nothing is wrong with the certificate; the runner simply has no reason to trust it.
The CA was installed in the wrong trust store
The wasted fix on this page. A certs.d entry does nothing for a build running inside BuildKit or for a curl in the next step, and the system store does not cover a daemon lookup that has its own directory. In our experience the second attempt at this error is the same certificate in a different place.
The directory name does not match the registry reference
The daemon looks up the directory by the host and port exactly as they appear in the image reference. A registry pulled as registry.internal:5000 needs the directory with the port; the same registry behind a proxy on 443 needs the directory without one. A mismatch leaves the file on disk and the daemon unaware of it.
The chain is incomplete rather than untrusted
A server configured to send only its leaf certificate leaves the client unable to reach the root it does trust, and the error reads the same way. Check what the server actually presents before assuming the CA is missing locally, because the fix is on the registry rather than on the runner.
How to fix it
Install the CA where the daemon looks
- Create the
certs.ddirectory for that registry, with the host and port spelled as your references spell them. - Write the CA certificate into
ca.crtin that directory. - Pull one image from that registry in the same job to confirm the trust took effect.
```Terminal
sudo mkdir -p /etc/docker/certs.d/registry.internal.example.com:5000
sudo cp internal-ca.crt /etc/docker/certs.d/registry.internal.example.com:5000/ca.crt
docker pull registry.internal.example.com:5000/acme/api:1.4.2
Add it to the system store for everything else Anything that is not the daemon reads the system bundle: curl, Helm, a language HTTP client, a scanner. Copy the CA into the system directory and refresh the bundle, in the same step that wrote the daemon copy, so the two can never drift apart. sudo cp internal-ca.crt /usr/local/share/ca-certificates/internal-ca.crt
sudo update-ca-certificates
curl -sSf https://registry.internal.example....

Powered by Readybot.io