Important
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.
To 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.
Everything 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.
The 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.
With all this said, let's begin!
The 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.
ssh-keygen -t ed25519 -f "~/.ssh/homelab_ed25519" -N "" -C "homelab access key"
Copy the public key to all remote hosts. This is manual, no need for a fancy script at this stage.
ssh-copy-id -i ~/.ssh/homelab_ed25519.pub [email protected]
ssh-copy-id -i ~/.ssh/homelab_ed25519.pub [email protected]
ssh-copy-id -i ~/.ssh/homelab_ed25519.pub [email protected]
ssh-copy-id -i ~/.ssh/homelab_ed25519.pub [email protected]
Test connectivity and make sure everything is working.
ansible all -m ping
You should see SUCCESS for each host. ping/pong
To avoid man-in-the-middle attacks, it's important to enable host key checking. Not critical for a home lab, but recommended.
ssh-keyscan -H 192.168.0.41 192.168.0.42 192.168.0.51 192.168.0.91 >> ~/.ssh/known_hosts
Update ansible.cfg
to enable host_key_checking
:
host_key_checking = True
This is just another test (of connectivity) and a confirmation that we have matching OS versions on all hosts.
ansible all -m shell -a 'cat /etc/lsb-release | grep DISTRIB_DESCRIPTION'
At the time of writing, all hosts are running Ubuntu 24.04.3 LTS.
NEXT: reaching "baseline" level, Tier ONE!
Run the baseline playbook:
ansible-playbook ansible/playbooks/baseline.yml
For the very first run you may need to add --ask-become-pass
to the command.
This will apply the following changes:
- Update package cache
- Upgrade all packages
- Install common packages
- Set system locale
- Set timezone to UTC
- Ensure sudo is installed
- Configure password-less sudo for admin user
- Secure SSH server
- Set kernel parameters for security
Run the Docker playbook:
ansible-playbook -i ansible/inventory/hosts ansible/playbooks/docker.yml
This will apply the following changes:
- Install required packages for Docker
- Install Docker packages on ARM
- Install Docker packages on x86_64
- Create docker group
- Add admin user to docker group
- Install Docker Compose
- Create docker config directory
- Configure Docker daemon
- Enable and start Docker service
- Verify Docker installation
- Verify Docker Compose installation
- Create Docker Compose files directory
- Check existing ACLs for Docker Compose directory
- Set additional permissions for Docker Compose directory
- Ensure setfacl is installed (for ACL management)
Run the Traefik playbook:
ansible-playbook -i ansible/inventory/hosts ansible/playbooks/traefik.yml
This will apply the following changes:
- Create Traefik directories
- Create Traefik network
- Create acme.json file with proper permissions
- Create Traefik configuration files
- Deploy Traefik container
If all goes well, you should be able to access the Traefik dashboard at https://traefik.l4n.io.
Note 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.
Next: Portainer!
Run the Portainer playbook:
ansible-playbook -i ansible/inventory/hosts ansible/playbooks/portainer.yml
This will apply the following changes:
- Create Portainer directories
- Stop and remove existing Portainer container if exists
- Check if Docker network exists
- Create Docker network if it doesn't exist
- Create Portainer configuration files
- Deploy Portainer container
If all goes well, you should be able to access the Portainer dashboard at https://portainer.l4n.io.
Note 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.
Next: Portainer Agent!
Run the Portainer Agent playbook:
ansible-playbook -i ansible/inventory/hosts ansible/playbooks/portainer-agent.yml
This will apply the following changes:
- Create Portainer Agent directories
- Stop and remove existing Portainer Agent container if exists
- Check if Docker network exists
- Create Docker network if it doesn't exist
- Deploy Portainer Agent container
Setting up the agents made it possible to link them to the main Portainer instance and manage them from one place.
Next: Docker Socket Proxy!
ansible-playbook ansible/playbooks/docker-socket-proxy.yml
This will apply the following changes:
- Create Docker Socket Proxy directories
- Stop and remove existing Docker Socket Proxy container if exists
- Check if Docker network exists
- Create Docker network if it doesn't exist
- Deploy Docker Socket Proxy container
This makes it possible for Homepage to auto-discover services running on the other servers.
Next: Homepage!
ansible-playbook ansible/playbooks/homepage.yml
This will apply the following changes:
- Create Homepage directories
- Stop and remove existing Homepage container if exists
- Check if Docker network exists
- Create Docker network if it doesn't exist
- Deploy Homepage container
If all goes well, you should be able to access the Homepage dashboard at https://home.l4n.io.
Finally! We've got something to look at! :)
At this stage you should be able to see all the services running on all servers, nicely represented.
This tier deploys Gitea, a self-hosted Git service, complete with a robust backup and restore system.
To deploy Gitea, use the provided management script:
./scripts/manage-gitea.sh deploy
The script will run the Ansible playbook, and upon completion, it will display the Gitea URL and initial admin credentials.
For security, user registration is disabled by default. To create your first user account, follow these steps:
-
Enable Registration: Open the
ansible/playbooks/gitea-with-backup.yml
file and comment out thegitea_disable_registration
variable:# In ansible/playbooks/gitea-with-backup.yml ... vars: ... # Disable user registration # gitea_disable_registration: "true"
-
Deploy Gitea: Run the deployment command again to apply the change:
./scripts/manage-gitea.sh deploy
-
Register Your Account: Navigate to your Gitea URL (e.g.,
https://git.l4n.io
) and register your user account through the web interface. -
Disable Registration: Once you have created your account, uncomment the
gitea_disable_registration
line inansible/playbooks/gitea-with-backup.yml
to secure your instance:# In ansible/playbooks/gitea-with-backup.yml ... vars: ... # Disable user registration gitea_disable_registration: "true"
-
Re-deploy: Run the deployment one last time to disable registration:
./scripts/manage-gitea.sh deploy
The playbook configures the user akora
as the administrator. If you need to reset the password for this user, you can use the management script:
./scripts/manage-gitea.sh reset-password
This command will generate a new random password for the akora
user and display it in the console.