Setting up self-hosted Expose (ngrok)
This is a part two of Django dev docker compose — in the prior entry, I ran into an issue where Django apparently has no equivalent of Laravel’s sail share
.
Since today’s Christmas, I rabbit holed a little into checking out how sail share
works and whether I could spin something similar up myself.
sail share
runs off of Beyond Code’s Expose. While it is written in PHP/Laravel (Zero), Expose itself is agnostic to the content it’s serving, so it’s 100% possible to set up Expose to point towards a Django project. This would be pretty similar to just setting up ngrok, aside from some difference in pricing (Expose vs ngrok).
While looking this over I decided I wanted custom subdomains, but I don’t want to pay for the pro plans of Expose/ngrok. Since Expose offers an open source option for self-hosting, I had a go at setting up my own Expose server.
Now I can use my nice personal domains for localhost
forwarding, as well as not having to deal with my ngrok URL changing every time I restart 🎉 Plus I can share my server with my friends, so we have our own private ngrok server without having to pay for everyone's memberships ☺️
Set up the server
- For the server, I wanted to try out Vultr so I set this up on a 0.5GB RAM $2.50/mo cutest tiniest babiest server in the world, but I’ll probably move this over to one of my spare bare metals shortly.
- First, set up Cloudflare to point the desired domain towards the server; it will very helpfully take care of HTTPS so we don’t have to worry about it.
- Set up files on the server —
# docker-compose.yml
version: '3'
services:
nginx:
image: nginx:1.15-alpine
ports:
- "80:80"
restart: always
volumes:
- ./nginx/conf:/etc/nginx/conf.d
expose:
image: beyondcodegmbh/expose-server:latest
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
port: 8080
domain: mydomain.app
username: ${ADMIN_USERNAME}
password: ${ADMIN_PASSWORD}
restart: always
volumes:
- ./database/expose.db:/root/.expose
# nginx/conf/expose.mydomain.app.conf
# taken from https://expose.dev/docs/server/ssl
server {
listen 80;
server_name mydomain.app *.mydomain.app;
location / {
proxy_pass http://expose:8080;
proxy_read_timeout 300;
proxy_connect_timeout 300;
proxy_redirect off;
# Allow the use of websockets
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
This sets up nginx to receive requests at port 80 and forward them to expose’s 8080. I also have additional static assets & files to replace the home page.
- Run the container —
ADMIN_USERNAME=admin ADMIN_PASSWORD=password docker compose up -d
Done! The server is now ready for connections, and there should have a nice admin interface at https://expose.mydomain.app/
protected by the selected admin username/password.
This is also where you can add users and generate auth tokens (read more at Expose).
Configure the client
Added the following to my codebase —
# docker-compose.yml
services:
web:
...
networks:
- expose
...
expose:
build: ./docker/expose
extra_hosts:
- 'host.docker.internal:host-gateway'
profiles:
- donotstart
command: share web:8000 --server-host=${EXPOSE_SERVER_HOST:-sharedwithexpose.com} --server-port=${EXPOSE_SERVER_PORT:-443} --subdomain=${EXPOSE_SUBDOMAIN:-mysubdomain} --auth=${EXPOSE_TOKEN}
networks:
- expose
networks:
expose:
driver: bridge
# docker/expose/Dockerfile
FROM beyondcodegmbh/expose-server:latest
RUN sed -i "s/'dns' => '127.0.0.1'/'dns' => true/g" config/expose.php
Unfortunately there doesn’t appear to be a way to pass this configuration value in, so instead we have a sad little Dockerfile. This is required to make Expose work nicely with Docker, otherwise it will not be able to resolve the container address
# .env
EXPOSE_SERVER_HOST=mydomain.app
EXPOSE_SERVER_PORT=443
EXPOSE_SUBDOMAIN=subdomain
EXPOSE_TOKEN=<EXPOSE_TOKEN>
In this example, when you run docker compose run expose
it’ll pop a subdomain up for you at https://subdomain.mydomain.app
tunneled to web:8000
🚀 Cool!
I added an extra helper to my ./d
helper script to shorten this to ./d expose
Maybe I can stop getting side-tracked now and get back to work on the application code!