--- # Tears one stack down off the current Unraid host: `docker compose down`, # then delete the project folder, then optionally drop its database. # # Included from main.yml when the stack's `state:` is `absent`. # # Ordering matters and is not obvious: `docker compose down` reads the compose # file to know what it is removing, so the project folder has to survive until # after that step. Delete the folder (or the stack's `src/` tree) first and # there is nothing left to tell Docker what belonged to the project — the # containers and networks have to be cleaned up by hand instead. # # What `down` takes with it, and what it deliberately doesn't: # - removed: the containers, and any network the project itself created # - kept: bind mounts. Appdata under /mnt/user/appdata/ survives, so # a removal is reversible by flipping `state:` back to `present`. # - kept: external networks (caddy-net, unraid_shared) — they belong to # another stack or to Unraid, not to this project. # - opt-in: named volumes (`remove_volumes: true`) and images # (`remove_images: local` or `all`). # # Not touched at all: the stack's `homelab/` path in Vault. That has its # own lifecycle and no reason to be destroyed by a redeployable teardown. - name: Check whether the stack's project directory is still present ansible.builtin.stat: path: "{{ stack_remote_dir }}/docker-compose.yml" register: stack_compose_file - name: Tear down stack with Docker Compose community.docker.docker_compose_v2: project_src: "{{ stack_remote_dir }}" state: absent # Named volumes and images are destructive beyond "stop running this # here", so they stay opt-in per stack rather than being implied by # `state: absent`. remove_volumes: "{{ stack.remove_volumes | default(false) }}" remove_images: "{{ stack.remove_images | default(omit) }}" remove_orphans: true # Absent already — a re-run after a successful removal, or a stack that was # never deployed to this host. Both are the desired end state, not an error. when: stack_compose_file.stat.exists - name: Remove stack project directory ansible.builtin.file: path: "{{ stack_remote_dir }}" state: absent # Unlike appdata, these are not state — they're copies of PNGs committed in # the repo, put there purely so the Docker page had something to render. # Nothing is lost by deleting them and a redeploy puts them back, so they # aren't opt-in the way volumes and images are. # # Listed from the repo checkout (main.yml) rather than globbed on the host: # `{{ stack.name }}-*.png` would also match the icons of any stack whose name # starts with this one's. - name: Remove stack icons ansible.builtin.file: path: "{{ item }}" state: absent loop: "{{ [unraid_icons_root ~ '/' ~ stack.name ~ '.png'] + stack_service_icons.values() | list }}" - name: Drop application database when: - stack.db is defined - stack.remove_database | default(false) no_log: true block: # Only the Postgres superuser credentials are needed here. The stack's own # Vault secrets are not read, so a teardown still works after its # `homelab/` path has been deleted. - name: Look up Postgres superuser credentials from Vault ansible.builtin.set_fact: pg_admin_secrets: >- {{ lookup('community.hashi_vault.vault_kv2_get', stack.db.admin_vault_path, engine_mount_point=vault_kv_mount, url=vault_addr, auth_method=vault_auth_method, role_id=vault_role_id | default(omit), secret_id=vault_secret_id | default(omit)).secret }} # Database before role: Postgres refuses to drop a role that still owns # objects, and the role owns this database. - name: Drop application database community.postgresql.postgresql_db: name: "{{ stack.db.name }}" login_host: "{{ stack.db.provision_host }}" login_port: "{{ stack.db.provision_port }}" login_user: "{{ pg_admin_secrets.POSTGRES_SUPERUSER }}" login_password: "{{ pg_admin_secrets.POSTGRES_SUPERUSER_PASSWORD }}" state: absent delegate_to: localhost become: false - name: Drop application database role community.postgresql.postgresql_user: name: "{{ stack.db.user }}" login_host: "{{ stack.db.provision_host }}" login_port: "{{ stack.db.provision_port }}" login_user: "{{ pg_admin_secrets.POSTGRES_SUPERUSER }}" login_password: "{{ pg_admin_secrets.POSTGRES_SUPERUSER_PASSWORD }}" state: absent delegate_to: localhost become: false