--- # Bootstrap Docker and Swarm cluster state for all swarm nodes. # -------------------------------------------------- # PRE-PLAY: Ensure NFS storage mounts are present before Swarm starts. # WHY first: Docker bind-mount paths (/mnt/homelab, /mnt/media) must exist # as live NFS mounts before any stack deploy runs. If absent, Docker # creates an empty local directory instead — silent wrong-state behavior. # WHY storage_mounts role: idempotent via ansible.posix.mount; safe to re-run # on already-mounted hosts (no-op when mount table already matches fstab). # -------------------------------------------------- - name: Ensure NFS storage mounts are present on all Swarm nodes hosts: swarm_hosts become: true gather_facts: true roles: - storage_mounts # -------------------------------------------------- # PRE-PLAY: Ensure the operational user is in the docker group on every node. # WHY separate play: the swarm_bootstrap role runs from `hosts: localhost` via # delegate_to, so `--limit swarm-node` silently skips that play. Running this # directly on swarm_hosts makes it independently targetable and idempotent. # WHY before the bootstrap play: docker daemon must accept socket connections # from ansible_user before any subsequent docker-cli tasks succeed. # -------------------------------------------------- - name: Ensure docker group membership for the operational user on all swarm nodes hosts: swarm_hosts become: true gather_facts: false tags: [docker-users, docker-install] tasks: - name: Add ansible user to the docker group ansible.builtin.user: name: "{{ ansible_user }}" groups: docker append: true - name: Bootstrap Docker Swarm cluster hosts: localhost gather_facts: false vars_files: - ../../group_vars/all.yml tasks: - name: Run swarm bootstrap role from the primary manager context ansible.builtin.include_role: name: swarm_bootstrap tags: [swarm-join]