{"id":44130256,"url":"https://github.com/akora/homelab","last_synced_at":"2026-02-08T22:07:43.542Z","repository":{"id":312138744,"uuid":"1045748319","full_name":"akora/homelab","owner":"akora","description":"A multi-tier home lab for services and experimentation as a self-contained IT environment","archived":false,"fork":false,"pushed_at":"2026-01-19T06:43:13.000Z","size":933,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-19T15:01:34.671Z","etag":null,"topics":["ansible","docker","docker-compose","gethomepage","gitea","homelab","homelab-automation","homelab-setup","homelabbing","n8n","portainer","syncthing","traefik","twingate"],"latest_commit_sha":null,"homepage":"","language":"Jinja","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/akora.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-08-27T16:50:19.000Z","updated_at":"2026-01-19T06:43:17.000Z","dependencies_parsed_at":"2025-08-29T00:55:55.187Z","dependency_job_id":"67355223-7541-4d81-bcf6-19eb81ef7dda","html_url":"https://github.com/akora/homelab","commit_stats":null,"previous_names":["akora/homelab"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/akora/homelab","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akora%2Fhomelab","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akora%2Fhomelab/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akora%2Fhomelab/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akora%2Fhomelab/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/akora","download_url":"https://codeload.github.com/akora/homelab/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/akora%2Fhomelab/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29246441,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-08T21:42:34.334Z","status":"ssl_error","status_checked_at":"2026-02-08T21:41:38.468Z","response_time":57,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ansible","docker","docker-compose","gethomepage","gitea","homelab","homelab-automation","homelab-setup","homelabbing","n8n","portainer","syncthing","traefik","twingate"],"created_at":"2026-02-08T22:07:42.893Z","updated_at":"2026-02-08T22:07:43.534Z","avatar_url":"https://github.com/akora.png","language":"Jinja","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Building a Multi-Tier Home Lab\n\n\u003e [!IMPORTANT]  \n\u003e IMPORTANT NOTE: I am fully aware that I'm exposing my username, some local paths and my internal network structure in the documentation and in the codebase. This is for educational purposes, I am OK with it.\n\nTo adjust the configuration to your own environment, please replace `akora` with your username and adjust the IP addresses and hostnames in the `inventory/hosts` file.\n\nEverything sensitive is stored in the `vault.yml` file (not included in the repository). Please check the `vault.yml.example` file for more details and examples.\n\nThe domain name I'm using locally is `l4n.io`. I own this domain and in Cloudflare this domain name is pointing to the local IP address where Traefik is running.\n\nWith all this said, let's begin!\n\n## Generate SSH keypair\n\nThe very first thing you need to do is to generate an SSH keypair. This will be used to enable passwordless authentication and will also allow Ansible to work seamlessly.\n\n```bash\nssh-keygen -t ed25519 -f \"~/.ssh/homelab_ed25519\" -N \"\" -C \"homelab access key\"\n```\n\n## Copy SSH key to remote hosts\n\nCopy the public key to all remote hosts. This is manual, no need for a fancy script at this stage.\n\n```bash\nssh-copy-id -i ~/.ssh/homelab_ed25519.pub akora@192.168.0.41\nssh-copy-id -i ~/.ssh/homelab_ed25519.pub akora@192.168.0.42\nssh-copy-id -i ~/.ssh/homelab_ed25519.pub akora@192.168.0.51\nssh-copy-id -i ~/.ssh/homelab_ed25519.pub akora@192.168.0.91\n```\n\n## Ping all hosts\n\nTest connectivity and make sure everything is working.\n\n```bash\nansible all -m ping\n```\n\nYou should see SUCCESS for each host. ping/pong\n\n## Security Hardening\n\nTo avoid man-in-the-middle attacks, it's important to enable host key checking. Not critical for a home lab, but recommended.\n\n### Add Host Keys to known_hosts\n\n```bash\nssh-keyscan -H 192.168.0.41 192.168.0.42 192.168.0.51 192.168.0.91 \u003e\u003e ~/.ssh/known_hosts\n```\n\n### Update Ansible Configuration\n\nUpdate `ansible.cfg` to enable `host_key_checking`:\n\n```ini\nhost_key_checking = True\n```\n\n## Check OS versions\n\nThis is just another test (of connectivity) and a confirmation that we have matching OS versions on all hosts.\n\n```bash\nansible all -m shell -a 'cat /etc/lsb-release | grep DISTRIB_DESCRIPTION'\n```\n\nAt the time of writing, all hosts are running Ubuntu 24.04.3 LTS.\n\nNEXT: reaching \"baseline\" level, Tier ONE!\n\n## Tier ONE: Baseline Configuration\n\nRun the baseline playbook:\n\n```bash\nansible-playbook ansible/playbooks/baseline.yml\n```\n\nFor the very first run you may need to add `--ask-become-pass` to the command.\n\nThis will apply the following changes:\n\n- Update package cache\n- Upgrade all packages\n- Install common packages\n- Set system locale\n- Set timezone to UTC\n- Ensure sudo is installed\n- Configure password-less sudo for admin user\n- Secure SSH server\n- Set kernel parameters for security\n\n## Tier TWO: Docker Installation\n\nRun the Docker playbook:\n\n```bash\nansible-playbook -i ansible/inventory/hosts ansible/playbooks/docker.yml\n```\n\nThis will apply the following changes:\n\n- Install required packages for Docker\n  - Install Docker packages on ARM\n  - Install Docker packages on x86_64\n- Create docker group\n- Add admin user to docker group\n- Install Docker Compose\n- Create docker config directory\n- Configure Docker daemon\n- Enable and start Docker service\n- Verify Docker installation\n- Verify Docker Compose installation\n- Create Docker Compose files directory\n- Check existing ACLs for Docker Compose directory\n- Set additional permissions for Docker Compose directory\n- Ensure setfacl is installed (for ACL management)\n\n## Tier THREE: Traefik \u0026 Portainer Installation\n\nRun the Traefik playbook:\n\n```bash\nansible-playbook -i ansible/inventory/hosts ansible/playbooks/traefik.yml\n```\n\nThis will apply the following changes:\n\n- Create Traefik directories\n- Create Traefik network\n- Create acme.json file with proper permissions\n- Create Traefik configuration files\n- Deploy Traefik container\n\nIf all goes well, you should be able to access the Traefik dashboard at \u003chttps://traefik.l4n.io\u003e.\n\n![Traefik Dashboard](assets/docs/images/Screenshot-Tier-ONE-TWO-THREE-FOUR-Traefik.png)\n\nNote on this screenshot that I have the exisiting services all configured, plus I have a few \"static\" routing configured as well, for my router and for my NAS.\n\nNext: Portainer!\n\nRun the Portainer playbook:\n\n```bash\nansible-playbook -i ansible/inventory/hosts ansible/playbooks/portainer.yml\n```\n\nThis will apply the following changes:\n\n- Create Portainer directories\n- Stop and remove existing Portainer container if exists\n- Check if Docker network exists\n- Create Docker network if it doesn't exist\n- Create Portainer configuration files\n- Deploy Portainer container\n\nIf all goes well, you should be able to access the Portainer dashboard at \u003chttps://portainer.l4n.io\u003e.\n\n![Portainer Dashboard](assets/docs/images/Screenshot-Tier-ONE-TWO-THREE-FOUR-Portainer.png)\n\nNote on this screenshot that what's captured here is a state _AFTER_ I linked all standalone \"agents\" to be able to see and manage all of the other servers from one place.\n\nNext: Portainer Agent!\n\nRun the Portainer Agent playbook:\n\n```bash\nansible-playbook -i ansible/inventory/hosts ansible/playbooks/portainer-agent.yml\n```\n\nThis will apply the following changes:\n\n- Create Portainer Agent directories\n- Stop and remove existing Portainer Agent container if exists\n- Check if Docker network exists\n- Create Docker network if it doesn't exist\n- Deploy Portainer Agent container\n\nSetting up the agents made it possible to link them to the main Portainer instance and manage them from one place.\n\n## Tier FOUR: Homepage (\u0026 Docker Socket Proxy) Installation\n\nNext: Docker Socket Proxy!\n\n```bash\nansible-playbook ansible/playbooks/docker-socket-proxy.yml\n```\n\nThis will apply the following changes:\n\n- Create Docker Socket Proxy directories\n- Stop and remove existing Docker Socket Proxy container if exists\n- Check if Docker network exists\n- Create Docker network if it doesn't exist\n- Deploy Docker Socket Proxy container\n\nThis makes it possible for Homepage to auto-discover services running on the other servers.\n\nNext: Homepage!\n\n```bash\nansible-playbook ansible/playbooks/homepage.yml\n```\n\nThis will apply the following changes:\n\n- Create Homepage directories\n- Stop and remove existing Homepage container if exists\n- Check if Docker network exists\n- Create Docker network if it doesn't exist\n- Deploy Homepage container\n\nIf all goes well, you should be able to access the Homepage dashboard at \u003chttps://home.l4n.io\u003e.\n\n![Homepage Dashboard](assets/docs/images/Screenshot-Tier-ONE-TWO-THREE-FOUR-Homepage.png)\n\nFinally! We've got something to look at! :)\n\nAt this stage you should be able to see all the services running on all servers, nicely represented.\n\n## Tier FIVE: Gitea Git Service\n\nThis tier deploys Gitea, a self-hosted Git service, complete with a robust backup and restore system.\n\n### Deployment\n\nTo deploy Gitea, use the provided management script:\n\n```bash\n./scripts/manage-gitea.sh deploy\n```\n\nThe script will run the Ansible playbook, and upon completion, it will display the Gitea URL and initial admin credentials.\n\n### Initial User Setup\n\nFor security, user registration is disabled by default. To create your first user account, follow these steps:\n\n1. **Enable Registration**: Open the `ansible/playbooks/gitea-with-backup.yml` file and comment out the `gitea_disable_registration` variable:\n\n   ```yaml\n   # In ansible/playbooks/gitea-with-backup.yml\n   ...\n   vars:\n     ...\n     # Disable user registration\n     # gitea_disable_registration: \"true\"\n   ```\n\n2. **Deploy Gitea**: Run the deployment command again to apply the change:\n\n   ```bash\n   ./scripts/manage-gitea.sh deploy\n   ```\n\n3. **Register Your Account**: Navigate to your Gitea URL (e.g., `https://git.l4n.io`) and register your user account through the web interface.\n\n4. **Disable Registration**: Once you have created your account, uncomment the `gitea_disable_registration` line in `ansible/playbooks/gitea-with-backup.yml` to secure your instance:\n\n   ```yaml\n   # In ansible/playbooks/gitea-with-backup.yml\n   ...\n   vars:\n     ...\n     # Disable user registration\n     gitea_disable_registration: \"true\"\n   ```\n\n5. **Re-deploy**: Run the deployment one last time to disable registration:\n\n   ```bash\n   ./scripts/manage-gitea.sh deploy\n   ```\n\n### Managing the Admin Account\n\nThe playbook configures the user `akora` as the administrator. If you need to reset the password for this user, you can use the management script:\n\n```bash\n./scripts/manage-gitea.sh reset-password\n```\n\nThis command will generate a new random password for the `akora` user and display it in the console.\n\n## Syncthing - Continuous File Synchronization\n\nSyncthing is a continuous file synchronization program that synchronizes files between two or more computers in real time. This setup deploys Syncthing with Traefik integration for secure remote access.\n\n### Deployment of Syncthing\n\n1. **Deploy Syncthing**:\n\n   ```bash\n   ansible-playbook ansible/playbooks/syncthing.yml\n   ```\n\n2. **Access the Web UI**:\n   - Open `https://sync.l4n.io` in your browser\n   - The default configuration disables remote discovery and relay servers for security\n   - The web interface is secured with your wildcard SSL certificate\n\n### Configuration\n\nKey configuration options (set in `ansible/roles/syncthing/defaults/main.yml`):\n\n```yaml\n# Port configuration\nsyncthing_gui_port: \"8384\"        # Web GUI port\nsyncthing_listen_port: \"22000\"    # File transfer port\nsyncthing_discovery_port: \"21027\" # Local discovery (UDP)\n\n# Security\nsyncthing_restrict_to_lan: true   # Restrict access to local network\nsyncthing_allowed_networks:\n  - \"192.168.0.0/24\"             # Adjust to your LAN subnet\n\n# Storage\nsyncthing_data_directory: \"/opt/docker/syncthing/data\"\nsyncthing_config_directory: \"/opt/docker/syncthing/config\"\n```\n\n### Security Features\n\n- **TLS Encryption**: All traffic is encrypted using the wildcard SSL certificate\n- **Local Network Only**: By default, access is restricted to your local network\n- **No Remote Discovery**: Remote discovery and relay servers are disabled\n- **File-based Authentication**: Uses the Syncthing web interface for user management\n\n### Adding New Devices\n\n1. Open the Syncthing web interface\n2. Click \"Add Remote Device\"\n3. Enter the Device ID of the remote device\n4. Select which folders to share\n5. Accept the connection request on the remote device\n\n### Troubleshooting\n\n- If you can't access the web interface, check if Traefik is running and the DNS is correctly pointing to your server\n- For sync issues, check the Syncthing logs:\n\n  ```bash\n  docker logs syncthing\n  ```\n\n- Ensure the required ports (8384, 22000, 21027/udp) are open in your firewall\n\n### Backup\n\nYour Syncthing data is stored in the configured data directory (`/opt/docker/syncthing/data` by default). Ensure this directory is included in your regular backup routine.\n\n## Twingate - Secure Remote Access\n\nTwingate provides zero-trust network access to your homelab resources. This setup deploys Twingate connectors on multiple hosts for redundancy and load distribution.\n\n### Deployment of Twingate Connectors\n\n1. **Configure Twingate Credentials**:\n\n   Update `ansible/inventory/group_vars/all/vault.yml` with your Twingate configuration:\n\n   ```yaml\n   # Twingate connector configuration\n   vault_twingate_connector_url: \"https://your-network.twingate.com\"\n   vault_twingate_connectors:\n     - name: \"twingate-rpi4-01\"\n       host: \"rpi4-01\"\n       log_level: 5\n       enabled: true\n       access_token: \"your-access-token-1\"\n       refresh_token: \"your-refresh-token-1\"\n     - name: \"twingate-rpi4-02\"\n       host: \"rpi4-02\"\n       log_level: 5\n       enabled: true\n       access_token: \"your-access-token-2\"\n       refresh_token: \"your-refresh-token-2\"\n   ```\n\n2. **Deploy Twingate Connectors**:\n\n   ```bash\n   ansible-playbook ansible/playbooks/twingate.yml\n   ```\n\n### Twingate Configuration\n\nKey configuration options (set in `ansible/roles/twingate-connector/defaults/main.yml`):\n\n```yaml\n# Docker image and network\ntwingate_connector_docker_image: \"twingate/connector:latest\"\ntwingate_connector_network_name: \"traefik-net\"\n\n# Storage directories\ntwingate_connector_data_directory: \"/opt/docker/twingate-connector/data\"\ntwingate_connector_config_directory: \"/opt/docker/twingate-connector/config\"\n\n# Logging\ntwingate_connector_log_level: 5  # 3=error, 4=warning, 5=notice, 6=info, 7=debug\n\n# Homepage integration\ntwingate_homepage_integration: true\n```\n\n### Twingate Security Features\n\n- **Zero-Trust Architecture**: All connections are authenticated and encrypted\n- **No Open Ports**: Connectors establish outbound connections only\n- **IPv6 Disabled**: Prevents STUN warnings and potential security issues\n- **Container Security**: Runs with `no-new-privileges` security option\n- **Network Isolation**: Integrated with Traefik network for secure communication\n\n### High Availability\n\nThe deployment creates multiple connector instances across different hosts:\n\n- **Primary Connector**: `twingate-rpi4-01` on rpi4-01\n- **Secondary Connector**: `twingate-rpi4-02` on rpi4-02\n\nThis provides redundancy and load distribution for your remote access needs.\n\n### Monitoring\n\nConnectors are automatically discovered by Homepage and include:\n\n- **Service Status**: Real-time connection status\n- **Container Health**: Docker container monitoring via Watchtower\n- **Log Monitoring**: Configurable log levels for troubleshooting\n\n### Twingate Troubleshooting\n\n- Check connector status in the Twingate Admin Console\n- View container logs:\n\n  ```bash\n  docker logs twingate-rpi4-01\n  docker logs twingate-rpi4-02\n  ```\n\n- Verify network connectivity and DNS resolution\n- Ensure access tokens are valid and not expired\n\n## n8n - Workflow Automation\n\nn8n is a powerful workflow automation tool that allows you to connect different services and automate tasks. This setup deploys n8n with secure authentication and SSL integration.\n\n### Deployment of n8n\n\n1. **Configure n8n Credentials**:\n\n   Update `ansible/inventory/group_vars/all/vault.yml` with your n8n configuration:\n\n   ```yaml\n   # n8n credentials\n   vault_n8n_basic_auth_user: \"admin\"\n   vault_n8n_basic_auth_password: \"your-secure-password\"\n   vault_n8n_encryption_key: \"your-32-character-encryption-key\"\n   ```\n\n2. **Deploy n8n**:\n\n   ```bash\n   ansible-playbook ansible/playbooks/n8n.yml\n   ```\n\n### n8n Configuration\n\nKey configuration options (set in `ansible/roles/n8n/defaults/main.yml`):\n\n```yaml\n# Docker image (always latest version)\nn8n_image: \"n8nio/n8n:latest\"\n\n# Network and storage\nn8n_network_name: \"traefik-net\"\nn8n_data_directory: \"/opt/docker/n8n/data\"\nn8n_config_directory: \"/opt/docker/n8n/config\"\n\n# Security settings\nn8n_basic_auth_user: \"{{ vault_n8n_basic_auth_user }}\"\nn8n_basic_auth_password: \"{{ vault_n8n_basic_auth_password }}\"\nn8n_encryption_key: \"{{ vault_n8n_encryption_key }}\"\n\n# Homepage integration\nn8n_homepage_integration: true\n```\n\n### n8n Security Features\n\n- **HTTP Basic Authentication**: Initial access control layer\n- **Credential Encryption**: All workflow credentials encrypted with AES-256\n- **SSL/TLS**: Automatic HTTPS via Traefik and Cloudflare certificates\n- **Network Isolation**: Runs in isolated Docker network\n- **Container Security**: Runs as non-root user (UID 1000)\n\n### Authentication Requirements\n\n**Basic Auth Credentials**:\n\n- Provides HTTP-level authentication before reaching n8n interface\n- Required because n8n runs in single-user mode without built-in user management\n\n**Encryption Key**:\n\n- Must be exactly 32 characters long\n- Used for AES-256 encryption of stored credentials and sensitive data\n- Critical for protecting API keys and passwords stored in workflows\n\n### Cross-Server Routing\n\nn8n runs on `rpi4-02` while Traefik runs on `zima-01`. Static routing configuration:\n\n```yaml\n# /opt/docker/traefik/config/dynamic/n8n.yml\nhttp:\n  routers:\n    n8n-router:\n      rule: \"Host(`n8n.l4n.io`)\"\n      service: n8n-service\n  services:\n    n8n-service:\n      loadBalancer:\n        servers:\n          - url: \"http://192.168.0.42:5678\"\n```\n\n### n8n Troubleshooting\n\n- Access the web interface at `https://n8n.l4n.io`\n- Check container status:\n\n  ```bash\n  ansible rpi4-02 -m shell -a \"docker ps | grep n8n\"\n  ```\n\n- View container logs:\n\n  ```bash\n  ansible rpi4-02 -m shell -a \"docker logs n8n --tail 20\"\n  ```\n\n- Verify Traefik routing:\n\n  ```bash\n  curl -I https://n8n.l4n.io\n  ```\n\n### Automatic Updates\n\nn8n is configured to use the `latest` tag and includes Watchtower integration for automatic updates. The container will automatically pull and deploy new versions as they become available.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fakora%2Fhomelab","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fakora%2Fhomelab","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fakora%2Fhomelab/lists"}