Replace a TLS Certificate in a Docker Container
Update TLS certificates used by a Docker container: replace bind-mounted files on the host, avoid the single-file mount trap, reload or restart the app, verify, roll back.
Docker has no certificate store of its own. Which files the application reads, and how it reloads them, is decided by the image. How you replace a certificate depends on how the files get into the container:
| How the files get in | How to replace them |
|---|---|
| Bind mount of a host directory (most Compose setups) | Replace the files on the host, then reload or restart the app |
| Docker/Compose secret | Update the secret source, then recreate the service |
| Files copied into the running container | Copy again - and move to a mount, because the files are lost when the container is recreated |
| Files baked into the image | Build and deploy a new image |
Before you begin
- Find the mounts and paths:
docker inspect <container> --format '{{json .Mounts}}', and the app's configuration for the certificate paths inside the container. - Find the user the app runs as, for file permissions:
docker exec <container> id. - Find out how the app reloads certificates (a reload command or signal), or whether it needs a restart.
- Back up the current files:
cp -p ./certs/fullchain.pem ./certs/fullchain.pem.bak(same for the key).
Bind-mounted files (Compose)
Example Compose service with the certificate directory mounted read-only:
services:
app:
image: example/app
volumes:
- ./certs:/etc/app/tls:ro-
Write the new files into the host directory:
cp fullchain.pem ./certs/fullchain.pem cp private.key ./certs/private.key chmod 0644 ./certs/fullchain.pem chmod 0600 ./certs/private.key # readable by the app's user, see "id" above -
Let the app pick them up - with its reload command, or a restart:
docker compose exec app <reload-command> # or docker compose restart appdocker compose up -ddoes not recreate a container just because the content of a mounted file changed.
Mount directories, not single files. Tools that replace a file usually create a new file (a new inode). A bind mount of a single file keeps pointing at the old one, so the container still sees the old certificate. A mounted directory always shows the current files.
Files copied into the container
If the files were put into the container with docker cp:
docker cp fullchain.pem <container>:/etc/app/tls/fullchain.pem
docker cp private.key <container>:/etc/app/tls/private.key
docker exec <container> chown <uid>:<gid> /etc/app/tls/private.key
docker exec <container> chmod 0600 /etc/app/tls/private.key
docker exec <container> <reload-command>These files live in the container's writable layer and disappear when the container is
recreated - the old certificate from the image or mount comes back. Move to a bind mount
or secret so the next recreation keeps the new certificate. docker cp into a path that
is itself a mount writes to the mount's source, and fails on a read-only mount.
Verify the new certificate
docker exec <container> openssl x509 -in /etc/app/tls/fullchain.pem -noout -subject -enddate
openssl s_client -connect app.example.com:443 -servername app.example.com -showcerts </dev/nullThe first command needs openssl in the image; otherwise check the file on the host.
The served certificate must show the new subject, issuer and expiry date. With several
replicas, check each one, and recreate the container once in a test environment to
prove the new state survives.
Rollback
Copy the .bak files back into the mounted directory (or copy the old files into the
container) and reload or restart the app the same way.
Common problems
- The new files exist only in the container's writable layer and vanish on recreation.
- A single-file bind mount still shows the old file.
- Host paths and container paths were mixed up.
- The app's user cannot read the key.
- The app ignores the reload signal - restart it.
- One replica still serves the old certificate.
Automating certificate replacement with aethercert
The aethercert Docker target copies certificate, chain, and key to configured paths inside a running container, then executes a configured in-container reload command or restarts the container. It does not redesign the container's persistence model.
See Deploy targets for the fields this preset takes, what it needs on the host, and how far it has been verified.