This repository manages the infrastructure for multiple hosts using Ansible and Docker Compose.
The project follows a host-based isolation strategy where each top-level directory corresponds to a specific host (e.g., mx1, web1).
Each host directory contains:
ansible/: Playbooks and roles for system configuration.containers/: Docker Compose definitions for services running on that host.
- Ansible: Configures the host system (OS hardening, users, firewall, etc.).
- Containers: The
system/containersrole copies the localcontainers/directory to/opt/containers/on the remote host. - Services: Services are deployed via
docker composeby iterating over the defined compose files. - Network: All containers typically attach to an external Docker network named
app-infra.
The repository currently manages the following hosts:
mx1web1web2web3web8
- Ansible: Must be installed on the machine running the playbooks.
- SSH Access: You need SSH access to the target hosts.
Navigate to the host's ansible directory and run the playbook:
cd <host>/ansible
ansible-playbook playbook.ymlSecrets are injected via environment variables using lookup('env', 'VAR_NAME'). Ensure these are set in your environment before running playbooks.
Common required variables include:
BORG_PASSPHRASEBORG_REPOSITORYMYSQL_PASSWORDBORG_HEARTBEAT_URL
The scheduled_run variable (controlled by the SCHEDULED_RUN environment variable) determines the scope of the execution:
SCHEDULED_RUN=true: Runs "heavy" tasks like OS hardening, APT updates, and Docker installation. This is typically used for maintenance runs.SCHEDULED_RUN=false(default): Skips heavy tasks for quick app deployments.
This infrastructure uses a migration system similar to Laravel's database migrations, but for system configuration. Migrations are one-time Ansible tasks that run only once per host.
- One-time execution: Each migration runs only once, tracked in
/opt/ansible/migrations.db - Per-host state: Each host maintains its own migration history
- Error handling: Playbook execution stops if a migration fails
- Versioned: Migrations use timestamped filenames (e.g.,
20240121_0001_setup_database.yml)
- Create a new
.ymlfile in<host>/ansible/migrations/ - Use timestamp format:
YYYYMMDD_NNNN_description.yml - Write standard Ansible tasks in the file
---
# Migration: 20240121_0001_create_app_user
- name: Create application user
user:
name: myapp
system: yes
shell: /bin/bash
- name: Create application directory
file:
path: /opt/myapp
state: directory
owner: myapp
group: myapp
mode: '0755'Migrations run automatically as part of the playbook execution via the system/migrations role. They execute before container deployment to ensure system prerequisites are met.
If a migration fails, the entire playbook stops to prevent inconsistent states.
This repository uses Renovate to keep dependencies up-to-date:
- Docker Images: Updates Docker images in
docker-compose.ymlfiles across all hostcontainers/directories. - Ansible Requirements: Manages Ansible collections and roles in
requirements.ymlfiles for each host.
To enable Renovate:
- Install the Renovate GitHub app on the repository.
For private registries, configure hostRules in renovate.json with appropriate credentials.
- Create a new directory in
<host>/containers/<service_name>/. - Add a
docker-compose.ymlfile.- Ensure it uses the
app-infranetwork:networks: app-infra: external: true
- Note: Do not include the top-level
versionproperty indocker-compose.ymlfiles.
- Ensure it uses the
- Add the service name to the
compose_fileslist in the host's Ansible variables (usually indefaults/main.yml) if it is not dynamically discovered.
system/containers: Core deployment role. Copiescontainers/to/opt/containers/and runsdocker compose up.system/config: System-level config including UFW rules andsystemd-resolved.system/backup: Configures Borgmatic backups.system/apt: Handles package updates and installation (usually conditional onscheduled_run).