Manual Installation
This is the advanced, step-by-step installation path. Prefer the automated Getting Started workflow for a supported first-time deployment.
- Before You Start
- Online Prerequisites
- Phase 1: Prepare the Admin Environment
- Phase 2: Provision the Docker Host
- Phase 3: Configure the Docker Host with Ansible
- Phase 4: Configure and Deploy Services
- Phase 5: Configure Networking
This guide deploys services to a Debian- or Ubuntu-based Docker host, normally a VM on Proxmox VE. It identifies where each command runs so the same workflow supports a combined Docker and admin host or a separate admin host.
Before You Start
Roles
| Role | Responsibility | Can be combined? |
|---|---|---|
| Proxmox host | Runs the Docker-host VM | Separate from the Docker host |
| Admin environment | Holds the infrastructure and private configuration repositories; runs Ansible | May be the Docker host |
| Docker host | Runs Docker Compose services | May be the admin environment |
The admin environment may be a separate Debian/Ubuntu workstation, a devcontainer running on that workstation, or the Docker host itself. A separate admin environment is recommended for a persistent deployment because the Docker host needs only read-only access to the private configuration repository. A new Docker-host VM needs a temporary admin environment until it is provisioned; it can become the admin environment afterwards in single-host mode.
Choose one of these operating models before continuing:
| Model | Admin environment | Docker host | Ansible command |
|---|---|---|---|
| Single-host | Docker host | Same machine | ansible/apply-localhost.sh |
| Separate admin host | Admin workstation or devcontainer | Separate VM | ansible/apply-homelab.sh --limit <host> |
In single-host mode, follow every step labelled Admin environment and Docker host on the same machine. In separate-admin-host mode, use SSH to run the Docker-host steps after Ansible has configured the VM.
Workflow Order
| Model | Follow this order |
|---|---|
| Single-host with a new VM | Use a temporary admin environment for Phase 2, then complete Phases 1, 3, 4, and 5 on the new Docker host. |
| Single-host with an existing Docker host | Complete Phases 1, 3, 4, and 5 on that host. |
| Separate admin host | Complete Phases 1, 2, and 3 from the admin environment, then complete Phases 4 and 5 on the Docker host. |
Command Locations
- Admin environment: A machine or devcontainer that runs Ansible and keeps the private configuration repository.
- Proxmox host: The hypervisor that creates and runs the Docker-host VM.
- Docker host: The VM that runs the container services.
Online Prerequisites
This setup uses a public domain name for published services and recognized TLS certificates. Free subdomains such as DuckDNS can work, but are not covered by this setup.
- Register a domain name, for example with OVHcloud.
- Transfer DNS zone administration to Cloudflare. Traefik uses Cloudflare for DNS-01 certificate renewal.
Phase 1: Prepare the Admin Environment
Where: Admin environment
In single-host mode, use a temporary admin environment to provision the Docker host, then complete this phase on the Docker host. In separate-admin-host mode, complete it on the dedicated admin workstation before provisioning or configuring the Docker host.
Choose the Tooling Environment
Use one of these options:
- Devcontainer: The repository includes a Dev Container
configuration. Use the Dev Containers extension
with Visual Studio Code. See Dev Containers for
troubleshooting. The image is large because it includes the administration
tooling configured in
ansible/inventory/group_vars/debian/vars.yaml. - Local tools: Use Debian/Ubuntu, including WSL. macOS supports remote
Ansible administration and the local
mac_baserole. Install Task using an official package, then verifytask --version; Phase 2 uses Task before Ansible configures the Docker host.
Prepare Repositories and Ansible
Clone the infrastructure repository and bootstrap Ansible:
Create the ignored configuration overlay from its examples, then edit the inventory and shared variables:
- Add each host and address to
config/ansible/inventory/inventory.yamlunderdebian, then assign the host to the groups for its required roles. - For a newly provisioned VM, set its
ansible_userto the same username asUSERNAMEinconfig/vm/proxmox/ubuntu-cloud.env. Ansible uses this account as the managed administrator by default. Overrideadmin_userper host only when the connection and managed accounts must differ. - Set
debian_base_ssh_key_fileinconfig/ansible/inventory/group_vars/debian/vars.yamland place the matching public key at that path, for example~/.ssh/id_ed25519.pub. - For a separate Docker host, add its current DHCP address as
ansible_hostin the ignored inventory for the initial Ansible run. It is safe to use this address before DNS is configured. Reserve the DHCP lease and configure the hostname in local DNS in Phase 5, then update the inventory to use the hostname. - Connect once with
ssh <initial-user>@<dhcp-address>and verify the host key before running Ansible. Do not disable SSH host-key verification.
See the Ansible README for inventory structure, bootstrap authentication, and troubleshooting options.
Phase 2: Provision the Docker Host
Where: Admin environment and Proxmox host
Install Proxmox VE first. See Proxmox VE.
From the infrastructure checkout on the admin environment, use the automated cloud-image workflow:
The tasks connect to the Proxmox host and create the VM. Record its DHCP address for the initial Ansible run. Reserve the lease before relying on hostname or service DNS; the initial Ansible run may use the current lease as described in Phase 1. For prerequisites, configuration, and autoinstall or manual alternatives, see Ubuntu VM installation.
Skip this phase only when an existing machine will be the Docker host.
Phase 3: Configure the Docker Host with Ansible
Where: Admin environment
Ansible configures Docker and the required host software. The Docker host must
be present in the inventory and the docker_hosts group.
For a separate Docker host, run:
For single-host mode, run this on the Docker host after its inventory hostname
has been assigned to docker_hosts:
Use --ask-become-pass when the initial account requires a sudo password. Use
a host limit for a new remote deployment so the playbook does not apply to
unrelated inventory hosts.
Docker data defaults to /srv/docker-volumes. Override
debian_docker_host_volumes_path in the host inventory before running Ansible
when using another path, then set DOCKER_VOLUMES to the same value in the
host’s Docker environment file.
Phase 4: Configure and Deploy Services
Prepare Docker-Host Repositories
Where: Docker host
The Docker host needs the tracked infrastructure repository and configuration. For a separate admin environment, give the Docker host a read-only deploy key to the private configuration repository:
The private configuration repository must contain
config/docker/<hostname>/. Do not store Git write credentials on a Docker
host.
For a Docker host using local-only configuration without a private configuration repository, use the local initializer on that host instead:
The initializer creates a minimal Traefik and Homepage profile, including the
shared, host, and mode-0600 Traefik environment files. It refuses to
overwrite an existing host configuration.
Configure Environment Files
Docker Compose variables use four scopes. labctl.py loads each existing file
in this order, so later files override earlier values:
| File | Purpose |
|---|---|
config/docker/.env | Common variables for every host and service |
config/docker/<hostname>/.env | Host-specific variables |
config/docker/.env.<service-name> | Common service-specific variables |
config/docker/<hostname>/.env.<service-name> | Host- and service-specific variables |
For a shared configuration repository, seed config/docker on the admin
environment from the examples before committing it to the private repository:
Replace all example values before deployment. Keep credentials only in ignored
config files or the password vault, never in tracked files or commands. Back
up the private configuration repository and use task backup-config for an
offline copy stored securely.
Example layout:
Configure Core Services
The minimal local profile selects Traefik and Homepage. Add other services to
config/docker/<hostname>/services.yaml only after completing their
configuration and secret requirements.
For a host using test.example.com, set these shared values in
config/docker/.env:
Set these host-specific values in config/docker/<hostname>/.env:
Set CLOUDFLARE_DNS_API_TOKEN in the host-and-service file
config/docker/<hostname>/.env.traefik. It needs Zone:Read and DNS:Edit
permissions for the zone containing MYDOMAIN. Keep CROWDSEC_ENABLED=false
for a minimal local deployment. Set it to true only after deploying CrowdSec
and generating CROWDSEC_BOUNCER_API_KEY in the same file.
Traefik obtains one certificate for MYDOMAIN and *.MYDOMAIN, which it uses
for all configured HTTPS routes.
For central authentication and SSO, see Authentik Getting Started.
Deploy Services
Where: Docker host
Select services in config/docker/<hostname>/services.yaml with state: up
When config is a private Git repository, synchronize it before deploying:
task pull-config-repo accepts only fast-forward updates, keeping configuration
updates and deployments as separate actions. For a local-only config
directory, run task docker:apply directly.
Verify Core Services
Where: Docker host
Confirm that the core containers remain running and are not restarting:
After Phase 5 DNS is configured, verify the normal access paths from the admin
environment. Replace <domain> with MYDOMAIN:
Use docker logs traefik and docker logs homepage when either check fails.
task docker:stop stops the configured containers without deleting their
persistent data. Run task docker:apply to start them again.
Phase 5: Configure Networking
Where: Router and local DNS service
Configure the following after the Docker host is online:
- Reserve a static DHCP lease for the Docker host.
- Configure local DNS to resolve
test.example.comand*.test.example.comto the Docker host. In AdGuard Home, use Filters > DNS Rewrites. - Set router DHCP clients to use the local DNS service, such as AdGuard Home.
- For external access only, forward HTTPS ports
443/TCPand443/UDPto the Docker host. Forward51820/UDPonly when using WireGuard.
Cloudflare DNS-01 lets Traefik create temporary _acme-challenge records for
Let’s Encrypt. Public DNS records and port forwarding are not required for
local-only HTTPS access.