docker-based server using custom subdomains over https
Clone this repo, or fork it first if you want to! Be sure to install the pre-commit hook following the instructions in that file. It will strip out the values from .env
and generate a env.sample
file with only the keys. It also will strip emails and the pilot token from traefik/traefik.yml
and generate a traefik/traefik.yml.sample
.
- host each service as a subdomain of a personal domain with cloudflare/letsencrypt
- run public maintained images with no modifications
- require minimal configuration and setup
- Netdata - Troubleshoot slowdowns and anomalies in your infrastructure with thousands of metrics, interactive visualizations, and insightful health alarms.
- Traefik - modern HTTP reverse proxy and load balancer that makes deploying microservices easy.
- ESPHome - system to control your ESP8266/ESP32 by simple yet powerful configuration files and control them remotely through Home Automation systems.
- code-server - Run VS Code on any machine anywhere and access it in the browser.
- personal-site - my personal site, built with Phoenix LiveView.
- AdGuard Home - network-wide software for blocking ads & tracking.
- Watchtower - container-based solution for automating Docker container base image updates.
- Portainer CE - lightweight ‘universal’ management GUI that can be used to easily manage Docker, Swarm, Kubernetes and ACI environments.
- Unifi controller - powerful, enterprise wireless software engine ideal for high-density client deployments requiring low latency and high uptime performance.
- Bookstack - simple, self-hosted, easy-to-use platform for organising and storing information.
- Wireguard - extremely simple yet fast and modern VPN that utilizes state-of-the-art cryptography.
- fail2ban - scans log files and bans IPs that show the malicious signs -- too many password failures, seeking for exploits, etc. *
- mosquitto - an open source (EPL/EDL licensed) message broker that implements the MQTT protocol versions 5.0, 3.1.1 and 3.1. *
- adguardhome-sync- Synchronize AdGuardHome config to a replica instance. *
- dashy - 🚀 A self-hostable personal dashboard built for you. Includes status-checking, widgets, themes, icon packs, a UI editor and tons more!
- whoogle - A self-hosted, ad-free, privacy-respecting metasearch engine
- edison - a little discord bot I run for some random tasks *
- lemmy - The official web app for lemmy **
*not exposed
** including the lemmy, lemmy-ui, postgres and pictrs containers
- dedicated server or PC
- docker and docker-compose
- personal domain with configurable sub-domains (eg. netdata.example.com)
Copy env.sample
to .env
and populate all fields in the COMMON
and EXTERNAL
sections.
Copy traefik/traefik.yml.sample
to traefik/traefik.yml
and fill in the ACME_EMAIL
and PILOT_TOKEN
variables (the pilot token is an optional property for Traefik Pilot, feel free to remove the section all-together).
Be sure to port forward :443
on your router to get access externally.
It is recommended to use the staging
cert resolver initially to avoid any potential rate limits from Let's Encrypt for any misconfigured services.
You will need to forward port 51820/udp, and be sure that it is not proxied through Cloudflare, as the CF proxy only allows HTTP traffic.
On initial startup, the container should generate 5 peer QR codes. You can view them with:
docker-compose logs -tf wireguard
On startup for the intial configuration the web interface will bind to port 3000
. If you have local access to the server, you can access it there (ie. 10.0.0.10:3000
), and then be sure that it does not bind to port 80
, but instead 3333
(defined in external labels for loadbalancer).
If you do not have direct network access to the server, you can launch the first time with the loadbalancer port to :3000
, configure it to bind to 3333
, then restart with the original port in the label configuration.
I set up a replica instance on a Raspberry Pi, so that I can have a backup DNS in case I need to take the server down for some reason. Just use the default config file (${CONFIG_DIR}/adguard/adguardhome-sync.yaml
) from the adguardhome-sync repo entering in the username and password for each, and you should be good to go! (Note: I turn off the stats and query log syncing because I find it useful to see what hits the fallback server)
I have set up only the single default network, and assigned it a subnet so that I can assign an IP to AdGuard for containers to reference as DNS. This allows more granular insight into container network activity, as they will no longer be agregated at the host level.
In order to see container names in AdGuard, set [/69.20.172.in-addr.arpa/]127.0.0.11
in the "Private DNS servers" field in AdGuard. This tells AdGuard to send PTR requests in the docker network to the internal docker DNS resolver. The main downside here is that any containers that run in "host" mode (in this case, just Home Assistant) will show up in AdGuard as the subnet gateway address for the internal network.
I have a couple of containers in "host" networking mode, this is to mainly make a few things work a little cleaner (UPnP, mDNS, DNS over IPv6). You can turn these off if you don't want any of these.
This stack utilizes Home Assistant via a VM managed in proxmox. I set it up using this script from tteck. The files in traefik/conf_example
with the info filled in should get you going to route to the external host.
Set the following into ${CONFIG_DIR}/home/configuration.yaml
:
Note: for running HA at a different IP, you will need to include the server IP running this compose stack where the <YOUR SERVER IP>
is.
http:
use_x_forwarded_for: true
trusted_proxies:
- 127.0.0.1
- <YOUR SERVER IP>
# cloudflare IPs for skipping in X-Forwarded-For header
- 103.21.244.0/22
- 103.22.200.0/22
- 103.31.4.0/22
- 104.16.0.0/13
- 104.24.0.0/14
- 108.162.192.0/18
- 131.0.72.0/22
- 141.101.64.0/18
- 162.158.0.0/15
- 172.64.0.0/13
- 173.245.48.0/20
- 188.114.96.0/20
- 190.93.240.0/20
- 197.234.240.0/22
- 198.41.128.0/17
If you have any devices (in my case a Xiaomi Air Purifier 3H) that don't seem to be able to discover, you may want to put them on the same VLAN. I did this by running the following commands:
echo -e '[keyfile]\nunmanaged-devices=none' | sudo tee -a /etc/NetworkManager/conf.d/10-globally-managed-devices.conf
sudo nmcli con add type vlan con-name enp0s25@vlan10 dev enp0s25 id 10
sudo service network-manager restart
I had previously set up a VLAN 10 tagged port on the switch for my server, with the base network as the secure network.
This should allow device discovery with the devices being in separate VLANs
This is set up to ban the real client IP in cloudflare, as well as the local iptables just for funsies (though my firewall does that job already). It is set up for all traefik applications by reading the access log, but home assistant for some reason decided to return a 200
even on a failed login. So there is another jail that reads the home assistant logs as well.
Copy fail2ban/cloudflare.conf.example
to fail2ban/cloudflare.conf
and fill in the cftoken
and cfuser
fields with the api key and email you use for cloudflare login.
Pull and deploy containers with docker-compose.
docker-compose pull
docker-compose up -d
This configuration is set up for a domain proxied through Cloudflare. You can remove this by removing the CF_API_KEY
and CF_API_EMAIL
environment cariables, as well as the entrypoints.websecure.forwardedHeaders.trustedIPs
config in traefik/traefik.yml
.
If you are using mediaserver locally and are not exposing any ports to the Internet, you can skip
this section or set IPALLOWLIST=0.0.0.0/0,::/0
in your .env
file.
Set the IPALLOWLIST
to only IP ranges that we want to explictly allow access.
This functionality can be enabled/disabled per service in docker-compose.external.yml
with the ipallowlist
middleware.
Uses traefik-forward-auth to handle OAuth through google. Just follow the setup instructions there, and fill in the relevant fields in env.
This stack uses the TFA host mode, which is hosted at AUTH_HOST
. Thus, the redirect URI you enter into google will be https://<AUTH_HOST>/_oauth
. This way you don't need to make an individual entry for every subdomain.
This functionality can be enabled/disabled per service in docker-compose.external.yml
with the basicauth
middleware.
Users can be added to basic auth in 2 ways. If both methods are used they are merged and the htpasswd file takes priority.
-
Add users in your
.env
file with theBASICAUTH_USERS
variable. -
Add users via htpasswd file in the traefik service.
The first user added requires htpasswd -c
in order to create the password file.
Subsequent users should only use htpasswd
to avoid overwriting the file.
docker-compose exec traefik apk add --no-cache apache2-utils
docker-compose exec traefik htpasswd -c /etc/traefik/.htpasswd <user1>
docker-compose exec traefik htpasswd /etc/traefik/.htpasswd <user2>
Started with setup from klutchell's mediaserver
Special thanks to brettinternet for the help and inspiration along the way.