HowTo: Use ESPHome in a Docker container with HTTPS

The ability to access ESPHome via HTTPS is an important consideration if you’re going to host ESPHome on your own Docker server. If you don’t have an encrypted connection, you can’t use USB flashing. Unfortunately the current Docker container does not include support for encrypted connections.

This brief post explains how I achieved this aim, and I hope it may help others.

I use docker-compose so will provide these on a per-container basis to simplify the explanation.

Encryption Certificates
I am using the certificates generated via HA’s integrated LetsEncrypt addon. There are plenty of guides on this step, so I won’t duplicate them. But, put simply, you’ll need to set it up for wildcards (like *.mydomain.com), and then create an automation to copy the resulting PEMs to your Docker host. You’ll also need to rename the files, to allow the system to automatically pick them up:

privkey.pem  -> mydomain.com.key
fullchain.pem -> mydomain.com.crt

Reverse Proxy
First you need nginx to act as a reverse proxy. It’ll accept encrypted connections from your browser, and then route the decrypted traffic to your target container. Here is a sample docker-compose.yaml:

version: '2'

services:
  nginx-proxy:
    container_name: nginx
    image: jwilder/nginx-proxy
    ports:
      - 80:80
      - 443:443
    volumes:
      - /var/run/docker.sock:/tmp/docker.sock:ro
      - /path/to/copied/certificates:/etc/nginx/certs
      - /storage/path/to/vhosts.d:/etc/nginx/vhost.d
    network_mode: host
    environment:
      - DEFAULT_HOST=<Docker fully-qualified Hostname>

You should be able to start nginx via docker-compose up -d which will create the container, and the nginx network.

ESPHome
The docker-compose.yaml is as follows:

version: '3'
services:
  esphome:
    container_name: esphome
    image: esphome/esphome
    volumes:
      - /path/to/esphome/config:/config
      - /etc/localtime:/etc/localtime:ro
    restart: always
    privileged: true
    networks:
      - nginx_default
      
    environment:
      - VIRTUAL_HOST=esphome.mydomain.com #This needs to be set to a unique subdomain that will be your URL.


networks:
  nginx_default:
    name: nginx_default

Make sure you update your DNS (or hosts file) so that your custom URL is resolvable.

Now fire it up via docker-compose up -d and let it install. I found that nginx only seems to pickup new containers on startup so, just to be sure, stop both, start nginx and then ESPHome.

You now should be able to access esphome via the URL you set above. So point your browser at https://esphome.mydomain.com and you should find yourself connected to ESPHome via HTTPS. Awesome!

The plot twist
But there is an issue. All of your tiles will show that the nodes are offline. This is because ESPHome relies on mdns (which is a broadcast/multicast protocol to do name resolution).

Unfortunately this isn’t easily fixed in either container or Docker itself.

mdns Repeater
The easiest solution, requiring the least amount of tweaking of broader Docker, was to install an mdns repeater on the network ESPHome is connected to. First, we need to find the id of the nginx network. From the command prompt, issue a docker network ls and scan down for the nginx_default network. It should look something like:

abcdefghijk nginx_default bridge local

Copy the ID to your clipboard, and then do an ifconfig | grep abcdefghijk

We’re looking for the interface name, which is probably just br-abcdefghijk, but it’s worth confirming. You then also need to find the interface name of your Ethernet adapter.

The docker-compose.yaml:

version: '3'
services:
  mdns-repeater:
    image: monstrenyatko/mdns-repeater
    container_name: mdns-repeater
    restart: unless-stopped
    command: mdns-repeater-app -f <Ethernet Adapter Name> <nginx-adapter name> #eg mdns-repeater-app -f eth0 br-abcdefghijk
    network_mode: "host"

Issue your final docker-compose up -d and you should find that your ESPHome tiles spring to life.

I hope this helps someone who wants to do the same!

5 Likes

Well you can if you plug the usb into your server.

Which server? ESPHome is running on an external Docker host.

When you say external, do you have no physical access?

It’s a server elsewhere in the house.

Too far away to plug it in?

Hey, each to their own.

My post above will take about 15 mins to do for anyone that needs it and, if repeatedly going to the server each time you add an ESP device is irritating, it might be worth doing.

Those who are happy taking the device to their server are in no way obligated to follow the guide.

Just offering a solution to the community in case someone else finds it helpful.

2 Likes

I am sure other people will find it useful! Some people seem to be getting the idea that plugging into your browser computer is the only way.

Thanks a lot @AnotherMatt, I was missing knowledge of the mDNS-Repeater. Deploying that one helped me to solve the issue :rocket:.

If someone whould have issues with:
ifconfig: command not found
then this command worked for me on Debian 11:
ip link show | grep abcdefghijk

Hello @AnotherMatt ,

I’m completely new to Nginx and I’m struggling to get SSL over ESPHome.
I viewed your tutorial and I have some questions about it.
How do you link the nginx_default network (used in the esphome container) with the nginx container ?
Don’t you have a configuration file for Nginx ?
What is the vhosts file you use in your nginx container ?
What is the default host value in your nginx container environment ?

I tried to set up my configuration through your example and with other examples seen on internet but without any success.

Thank you.

That should be the default container created when nginx first starts. If you get something else, adjust ESPHome accordingly.

That particular container is built specifically for the task at hand and manages its own config files. You can change and override as necessary, but the default should work fine.

It’s blank. As above, this container will manage itself.

It’s defined in the docker-compose.yaml above. I have mine set to the FQDN of the docker host.

This container isn’t just a vanilla nginx install. It’s specifically built to dynamically forward proxy containers with an environment variable set. Make sure you don’t take any configs from other installs.

Full documentation for this nginx container can be found here: GitHub - nginx-proxy/nginx-proxy: Automated nginx proxy for Docker containers using docker-gen

Thanks for the write up and mdns repeater tip. Works perfectly with my Traefik proxy; no more ‘offline’ statuses.

Thankyou for the instructions! I changed it slightly to work for me which may help others;

I have a docker host running on Raspberry Pi but it is already hosting other services that use http/80 and https/443 (in my instance another docker container)

I tried getting the jwilder/nginx-proxy to work with a different https port other than 443 and although it worked for the main ESPHome website, things like view logs on devices failed to work (didn’t have much luck trying to figure custom nginx-proxy config files) so I did the following:

  1. Added additional IP address onto Raspberry pi network configuration
  2. Created a separate letsencrypt certificate for this new additional IP address (e.g. esphome.mydomain.com). This will likely work with wildcard or SAN certificates but for me I was happy to have a separate dedicated certificate for this.
  3. Configured the existing docker that was using https port 443 to the original IP address the Raspberry pi had rather than just the -p 443:443 mapping (e.g. -p 192.168.0.1:443:443). If you don’t do this the additional IP address you added in step 1 would already be used by this existing docker container and unavailable for the nginx-proxy container.
  4. In the nginx-proxy docker-compose.yaml or appropriate command line, the environment DEFAULT_HOST line, the FQDN was of the new name (e.g. esphome.mydomain.com), I didn’t map port 80 either (see below)
  5. In the esphome docker-compose.yaml or appropriate command line, the environment VIRTUAL_HOST was set to the same FQDN as the DEFAULT_HOST (e.g. esphome.mydomain.com) and instead of using nginx_default for the network option I used the network-mode: host option. As esphome and nginx-proxy are running on the same host (at least for me!) it didn’t need the mdns Repeater either.
  6. In home assistant added the URL to the dashboard (e.g. https://esphome.mydomain.com) with the mdi:chip icon.

Unencrypted communication

I could be misunderstanding this, but one more area I was concerned about is that the default http://esphome.mydomain.com:6052 (or if over port 80 via the ngix proxy setup as per original post) is over http so anything that can communicate over that can see all the devices and see the secrets.yaml file. That’s why I didn’t see the need to open up port 80 for the nginx-proxy container in step 3.

I’m sure there are many ways to restrict this like firewall and vlans (directly to the esphome container) and you’d need to ensure all the esp devices are able to still access it, but I saw in the esphome documentation an example with the environment variable USERNAME and PASSWORD so used those on the docker-compose of the esphome container and now it prompts you to log in to esphome!