My portable, private home server infrastructure.
Diagram Source
digraph G {
fontname="Helvetica,Arial,sans-serif";
node [fontname="Helvetica,Arial,sans-serif", shape=rect, style="rounded,filled", fillcolor="#2d2d2d", fontcolor=white, color=white];
edge [fontname="Helvetica,Arial,sans-serif", color=white, fontcolor=white];
bgcolor="#1f1f1f";
rankdir=TB;
nodesep=0.7;
ranksep=0.9;
compound=true; // Allows edges to connect to clusters
splines=polyline;
internet [label="Internet", shape=none, fillcolor=none];
subgraph cluster_external {
bgcolor="#601000";
label="External Compose";
fontcolor=white;
color=white;
style=rounded;
public_rp [label="External Reverse Proxy"];
website [label="Website\n(public.duckdns.org)"];
headscale [label="Headscale\n(hs.public.duckdns.org)"];
// Connections
public_rp -> website;
public_rp -> headscale;
}
internet -> public_rp [lhead=cluster_external];
// Cluster_Tailnet Cluster
subgraph cluster_tailnet {
bgcolor="#013312";
label="Tailnet";
fontcolor=white;
color=white;
style=rounded;
// Tailnet nodes.
phone [label="Phone"];
laptop [label="Laptop"];
vps [label="VPS"];
phone -> laptop [dir=none, constraint=false];
laptop -> vps [dir=none, constraint=false];
// Connect mesh to tailscale_node
{ phone; laptop; vps } -> docker_ts_node [dir=none];
// Cluster_Tailnet Cluster
subgraph cluster_internal {
bgcolor="#32467a";
label="Internal Compose";
fontcolor=white;
color=white;
style=rounded;
docker_ts_node [label="Docker Tailscale Node\n(*.private.duckdns.org)"];
private_rp [label="Internal Reverse Proxy"];
service1 [label="Service 1"];
service2 [label="Service 2"];
service3 [label="Service 3"];
docker_ts_node -> private_rp;
private_rp -> {service1; service2; service3};
}
}
// Connect Headscale to the Cluster_Tailnet
headscale -> docker_ts_node [lhead=cluster_internal];
}Usage: make [target] Docker compose targets: up Spin up the public and private compose stacks. down Tear down the public and private compose stacks, removing dangling volumes. pull Pull service images. Tailscale targets: nodes List existing nodes in the tailnet. preauth Generate a new preauth key. pull Pull service images. Authentication: secret Generate a new argon2 hashed password. Run this as `PASS='mypwd123' make secret`. Nextcloud: rescan Reload photos in nextcloud. Default target: help Show this help message.
Copy the .env_template to .env, replacing the environment variables with your own information.
# Domain for public-facing services.
PUBLIC_DOMAIN=mydomain.duckdns.org
# Entrypoint for Tailscale services.
CONTROL_DOMAIN=control.duckdns.org
# ACME email and user.
EMAIL=admin@mail.com
USER=admin
# DNS token for cert mgmt.
DUCKDNS_TOKEN=
# Pre-authentication key that allows tailscale client to auto-register with headscale.
TS_AUTHKEY=
# Nextcloud DB password.
DB_PASSWORD=Start by creating a headscale user in the control plane.
docker exec headscale headscale users create myuserNext, run make preauth to create a short-lived preauthentication token. This will be used to connect nodes to the tailnet. Add this to the $TS_AUTHKEY env variable in .env and restart the compose stack. After a few minutes, run make nodes to list connected nodes. The docker tailscale node should have automatically connected!
To add additional nodes, ensure that each device has the tailscale client installed and the tailscaled daemon running. Run tailscale up to connect the machine:
sudo tailscale up \
--login-server https://hs.${PUBLIC_DOMAIN}:443 \
--authkey yourauthkeygoeshere \
--accept-routes \
--accept-dns=falseRun PASS='mypwd123' make secret to generate an argon2 hash of your password.
Copy data/authelia/users_database.template.yml to data/authelia/users_database.yml, replacing the password section with the argon2 hash we just generated.
# List of users
users:
authelia:
disabled: false
displayname: "Authelia User"
# Password is authelia
password: "$6$rounds=50000$BpLnfgDsc2WD8F2q$Zis.ixdg9s/UOJYrs56b5QEZFiZECu0qZVNsIYxBaNJ7ucIL.nlxVCT5tqh8KHG8X4tlwCFm5r6NTOZZ5qRFN/"
email: "authelia@authelia.com"
groups:
- "admins"
- "dev"