Configuring certs in Caddy in a container for development

1. The problem I’m having:

I’m setting up Caddy as a containerized static web server/reverse-proxy in a set of containers for local development and eventually deployment to a production server. I’ve got it all working pretty well — except for certificate generation. Despite Caddy’s vaunted “It all just works” philosophy, it doesn’t just work, I assume because the containerized Caddy instance can’t communicate with my local machine to register any autogenerated certs.

I’m running on Linux, but I can’t assume the people who will be doing development on this will be on any particular OS, so I need a solution which will work for everybody with minimal configuration.

I was hoping I could get this running on localhost. When that failed I registered a local.webdev.com address in my /etc/hosts file. Finally I fell back to registering a local.larp.buzz address in my publicly available DNS provider. I couldn’t get anything to work. Here are my thoughts:

I assumed by registering an A record (as 127.0.0.1) and an AAAA record (as ::1) that the ACME client would automatically provision a cert. But it consistently gets
{"level":"error","ts":1729860457.8742154,"logger":"tls.acme_client","msg":"challenge failed","identifier":"local.larp.buzz","challenge_type":"tls-alpn-01","problem":{"type":"urn:ietf:params:acme:error:dns","title":"","detail":"no valid A records found for local.larp.buzz; no valid AAAA records found for local.larp.buzz","instance":"","subproblems":[]}} despite nslookup working fine for those records. I’m also worried about hitting that endpoint repeatedly as I bring services up and down; I could switch to the development endpoint for development but ACME requests you not to do that and instead run a local CA yourself, and at that point my development compose stack is starting to divert significantly from the production stack in ways I’m not happy about.

I assume I could just manually generate my own certificate and register it. Is that the recommended practice for this? Should I run off of localhost or is registering a development domain (either locally or in DNS) the better choice? Or is there some clever way of integrating the container with the local machine that avoids this problem?

2. Error messages and/or full log output:

{"level":"error","ts":1729860457.8742154,"logger":"tls.acme_client","msg":"challenge failed","identifier":"local.larp.buzz","challenge_type":"tls-alpn-01","problem":{"type":"urn:ietf:params:acme:error:dns","title":"","detail":"no valid A records found for local.larp.buzz; no valid AAAA records found for local.larp.buzz","instance":"","subproblems":[]}}

3. Caddy version:

caddy:2.8-alpine

4. How I installed and ran Caddy:

FROM caddy:2.8-alpine
WORKDIR /proxy
COPY Caddyfile ./
COPY --from=build-stage /app/dist ./webroot
EXPOSE 80
EXPOSE 443
CMD ["caddy", "run", "--config", "/proxy/Caddyfile"]

a. System environment:

Docker

b. Command:

docker compose up

c. Service/unit/compose file:

  web:
    build: ./web
    ports:
      - "80:80"
      - "443:443"

d. My complete Caddy config:

localhost, local.larp.buzz {
  encode zstd gzip
  handle_path /api/* {
    reverse_proxy http://api:80
  }

  handle {
    root * /proxy/webroot
    try_files {path}.html {path} /index.html
    file_server
  }
}

Yeah, Caddy isn’t able to add its cert to your host machine’s trust store, because it’s in a container. I can’t escape the container, that would be bad.

You can find instructions to copy out the root CA cert to install it on your host machine here:

This happens because your domain looks like it could be a real public domain, so Caddy tries to issue the cert from Let’s Encrypt or ZeroSSL. I assume this isn’t a real domain, so you’ll need to add tls internal to your config to force Caddy to issue the cert using its internal CA.

It would not, because Let’s Encrypt can’t reach your server by connecting to 127.0.0.1, it wouldn’t be able to prove you control that domain. For the ACME HTTP and TLS-ALPN challenges, the A and/or AAAA records must actually point to your server (via a real public IP address).

There is also the ACME DNS challenge which allows you to get a cert even when the server isn’t publicly reachable, but it requires you to build Caddy with a DNS plugin for your DNS provider and authenticate Caddy to it with an API key or whatever.

2 Likes

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.