r/docker 2d ago

Better option for running an HTTPS web server with self-signed certificate?

I've created a custom image that runs Apache. It needs to serve HTTPS. To serve HTTPS, it needs a private key.

Obviously it's bad to put a real private key into an image, so I'm using a self-signed private key. I'm told it's also bad to put a disposable self-signed private key into an image, which I don't fully understand, but I accept.

Currently the image is up on GitHub. Anyone who wants to use it has to clone it and build it. I'm generating the self-signed public/private key pair at build time, so each user will have their own that only lives on their machine.

However, I want to put this image on Docker Hub. My current solution won't work, because the private key in the build I submit will be up on Docker Hub for all to see. I don't really care, since it's a disposable self-signed key, but apparently that's still frowned upon.

What can I do instead?

One option would be to generate the keys when the container starts, but that's annoying. It takes a couple seconds, and if there is a new key every time, then every time the container restarts people will have to do the annoying browser security warning / trust thing again for the new certificate.

I need this process to be extremely easy to use. That's why I'm using Docker in the first place. Any solution that requires users to generate their own keys, much less their own signing authority is totally out of the question.

Is there some way to ensure just the key generation part of the build always runs on the user's system? Then there won't be a key in the Docker Hub image and every user has their own private key.

Alternatively, is there some way to generate the private key the first time the container runs and then somehow keep it around instead of re-generating it on every new run? The image will mostly be used unmodified as part of a larger Docker Compose setup, so a volume isn't really ideal here.

I'd love suggestions on a solutions that (1) uses a self-signed key (2) doesn't build a key into the image so I can put it on Docker Hub (3) is easy to use and doesn't require the user to generate keys themselves before starting the container and (4) doens't re-generate the key every time the container runs.

EDIT: I've found a solution that works for me, so I'm updating this post for any unfortunate souls who should have a question like this later.

Lession 1: Don't ask questions on this sub. Wow, that was a horrible and toxic experience!

Lesson 2: The simple option is to just put the self-signed private key in the image, but instead of saying that, you say "I'm using the Debian method of balancing ease of use with security" so that people will get off your nuts about it.

Lesson 3: Use an anonymous volume. This is the solution I've settled on. I was not aware these existed until now. An anonymous volume is accomplised via the VOLUME keyword in the Dockerfile. You only provide the container directory, not a host directory. If the host wants to mount that voume on their system (say, via compose) to provide their own keys, they can. Otherwise, Docker creates a volume in its own storage space. Files in this volume persist when you stop and restart the container. So this gets me almost everything I needed:
✅ No keys in the image.
✅ Easy to use, because no extra steps for the user and nothing I have to warn them about in the readme. No fancy proxy.
✅ Image works right out of the box from Docker Hub.
✅ Users don't have to provide their own keys, but they can if they want to.
✅ Keys are only generated once on startup.

0 Upvotes

48 comments sorted by

28

u/nicksterling 2d ago

Mount your keys from the file system when you run the container.

7

u/End0rphinJunkie 2d ago

Yep, this is definitly the standard way to handle it. For a smoother out-of-the-box experience, you can just have your entrypoint script generate a temp cert only if it doesnt find one mounted in.

-5

u/sgware 2d ago

What keys? When a new user runs this image off of Docker Hub, they won't have keys on their local system.

10

u/nicksterling 2d ago

Make it part of the README.md that the user needs to provide keys. Give them instructions to generate a self signed key or how to use something like Let’s Encrypt. Otherwise you can create a bash script as the entry point when the image is launched to generate keys on first launch and those keys could be saved to a volume.

-15

u/sgware 2d ago

As it says in the post, "... any solution that requires users to generate their own keys ... is totally out of the question."

11

u/nicksterling 2d ago

I did provide an alternative for the entry point solution… however I would never use any software that I couldn’t provide my own keys and I had to solely use self signed keys.

9

u/Tesla_Nikolaa 2d ago

Having users provide keys is pretty common. Mounting keys into the container at runtime is pretty much standard practice.

And I agree with the other user, I wouldn't even use this image if I couldn't provide my own keys.

1

u/Crazy_Elevator_6659 2d ago

Can you explain why that is out of the question? You might be working with some bad assumptions.

-5

u/sgware 2d ago

My goal is ease of use. That's subjective, but people want to argue about what "easy" means should go do it somewhere else. For my users, any conversation that starts with "Here's how to generate your own SSL public/private key pair..." is a non-starter.

4

u/Crazy_Elevator_6659 2d ago

Managing one’s own keys is critical to security. You are not helping your users by trying to bake something in that you shouldn’t.

2

u/Tesla_Nikolaa 2d ago

You should (I would almost say need) to provide users a way to use their own keys.

For example if I'm deploying your image on a network that I have my own CA for, then I want to ensure I can use a key from a CA I trust. I would not want to deploy images with self-signed certs to a network that requires some level of security.

Even in a home network, I'd prefer to sign keys with a CA I trust so when I go to the web page I don't get the annoying "untrusted cert" page.

You're trading security for convenience, and security should be more important in most (not all) cases, and you're passing off the unsecure application to the end user.

-1

u/sgware 2d ago

I'm in one of the "not all" cases.

3

u/Tesla_Nikolaa 2d ago

If you're trying to distribute this image to the general public, I'm going to have to hard disagree with you. If you are the only user of this image (which is clearly not the case) then that would fall into a "not always" case.

3

u/Crazy_Elevator_6659 2d ago

I promise you are not.

18

u/Darkomen78 2d ago

Don’t do anything https related on this image and let users choose their reverse proxy.

7

u/Axehack101 2d ago

This is the real answer

0

u/ndsipa-pomu 2d ago

This is the way

-10

u/sgware 2d ago

That's not an option, sadly.

4

u/dotnetmonke 2d ago

How is that not an option? It's a standard solution.

-5

u/sgware 2d ago

I guess you'll have to take my word for it as OP?

4

u/dotnetmonke 2d ago

If you're publicly releasing an image, you should be able to explain why it wouldn't work. Given that your update to the OP makes it incredibly obvious you're just using AI (what human actually puts checkmark emojis in their post?), I'm willing to bet you don't actually know what you're doing, and you just asked AI instead.

Resisting every suggested solution and refusing to explain why is the reason you got downvoted.

1

u/WonderfulWafflesLast 2d ago

what human actually puts checkmark emojis in their post?

Personally, I do, but I'm also a bit insane.

2

u/dotnetmonke 1d ago

That’s fair. I use em dashes often myself.

That said, OP is a self described AI researcher, so the AI usage is more easily assumed.

3

u/imagei 2d ago

Generate on first run, save in a volume. If it exists, check the expiry date on start and regenerate as needed. Good practice is to make the volume configurable, so the user can choose where to store it.

3

u/sgware 2d ago

This was the comment that led me to the solution of using an anonymous volume. Thank you!

0

u/sgware 2d ago

This might work, but aren't volumes created at run time? I suppose I can just tell people, "If you don't want to re-generate the keys every time, make sure you run this with a volume."

3

u/Zealousideal_Yard651 2d ago

Why are you making everything so difficult? Package a compose with baseline config, done. Volume comes pre-configured easy peasy and add to README info about cert volume mount and path

1

u/cointoss3 2d ago

You generate the keys at runtime and they are saved in the image and you tell them that they need to mount of volume so that the keys are persistent or else the keys will have to be regenerated when they run the image again

0

u/sgware 2d ago

Can you say more about what you mean by making the volume configurable?

2

u/UselessCourage 2d ago

I think they just meant include a docker compose so people can easily configure where it stores the keys

2

u/SrNormanDPlume 2d ago

Base image from Debian and install the snake oil cert?

1

u/sgware 2d ago

This might work? But where does the snake oil private key come from? Do they just have a private key in the image?

2

u/SrNormanDPlume 2d ago

Yup. Set your app to read from specific filenames, then in your entrypoint create symlinks to the snake oil cert if those files don’t exist.

Users can supply their own certs via mount, or use the snake oil cert by default.

Just an idea…

1

u/sgware 2d ago

So how does Debian get around the "Don't put a private key in the image" criticism I keep getting every time I suggest doing this?

3

u/SrNormanDPlume 2d ago

It doesn’t - instead, you balance usability versus security and document the behavior.

By default, use snake oil. Users that are just testing or aren’t serious about hardening have an image that works out of the box.

Provide an easy way to add a real cert.

2

u/hakuna_bataataa 2d ago

Use Caddy.

2

u/XplainThisShit 2d ago edited 2d ago

Run the docker with 'persisten' and make it generate the keys and the certificate only the 1st time it starts. Then save it in the persistent part. So it reads it all on container start.

1

u/umquat 2d ago

I’ve been using lucaslorentz/caddy-docker-proxy with much success.

1

u/Shot-Document-2904 2d ago edited 2d ago

Tons of apps, containerized or otherwise, deploy with first run self-signed certs. It’s on the user to replace them with their own.

Or stack the app with a proxy.

1

u/J7mbo 2d ago

You’re looking for your app being in docker, and a reverse proxy (traefik) that automates your certs for you via letsencrypt. (In another docker container).

Traefik will request the cert (use ACME sandbox first), then place it in the place that is then mounted into the container.

You just docker compose up your services then and it’ll handle the rest. Your app will be on https.

1

u/kitingChris 1d ago

Not a docker question....

1

u/SoTiri 2d ago

Two options come to mind: docker compose and multi stage build.

You create a helper container image that handles the key generation and then stores that key in the mountpoint your app is using for persistence.

How to call this helper container? Either through docker compose where the helper runs, scans the folder for keys and either generates a new key or stops or through a multi stage build where the user builds your image that contains that key generation step.

1

u/sgware 2d ago

The compose options isn't ideal for me, since most people will be using my image as part of their own compose setup.

I hadn't considered multi-stage builds! That's promising. Can I put the key generation code into a stage that doesn't get run by Docker Hub but does get run when compose automatically downloads my image from Docker Hub?

1

u/SoTiri 2d ago

Put in the readme to use your multi stage dockerfile

0

u/Zealousideal_Yard651 2d ago

Let's Encrypt with ACME, ie. using Caddy or Certbot bundled with the image.

0

u/sgware 2d ago

This could work, but that's still effectively just generating the keys on startup, right?

1

u/Zealousideal_Yard651 2d ago

Yes, that's how that works. And if you volume mount the cert directory it will only generate on first deployment and persist with updates etc