Deploy with rootless podman + Quadlet on emily
Retargets the build and the playbook off rootful Docker on monoco, following the conventions already in place on emily's other services: Quadlet units in ~/.config/containers/systemd, per-service data in ~/podman-data, and a shared Caddy that every service registers itself with — a network drop-in plus a marked block in its Caddyfile. Nothing is published on the host; Caddy reaches the containers by name over mozimo_net. - Makefile: podman build + push instead of docker buildx, PLATFORMS=linux/arm64 (both hosts are aarch64). podman has no built-in QEMU the way buildx's builder image does, so a foreign-platform failure now points at qemu-user-static. - deploy.sh: pass COMMIT= to make, which the Makefile actually reads; it was passing TAG=, so the tag silently came from `git rev-parse` instead. - ansible: hosts emily, become false, XDG_RUNTIME_DIR for rootless podman and systemctl --user. Docker modules become Quadlet templates with user-service handlers, and the network role now ships mozimo.network plus the Caddy drop-in rather than calling docker_network. - UserNS=keep-id maps the container's uid 1000 (node) and root (vince) onto the host user, so PocketBase and Vince write host-owned files instead of subuid-owned ones; the play asserts the pinned uid still matches the host. - ansible/README.md: the unit layout, the service-registration convention, and the monoco -> emily migration (PocketBase 23 MB + Vince 119 MB, DNS cutover, rollback). Verified by a check-mode rehearsal against emily (13 ok / 8 changed / 0 failed) and a disposable Quadlet unit on that host proving the uid mapping, the EnvironmentFile and the isolated network. The real build and cutover have not been run.
This commit is contained in:
1 parent
1bc1d5f753
commit
d04314ab1c
20 files changed
+535
-127
No files matched your search
@@ -0,0 +1,17 @@
|
||||
---
|
||||
# Handlers for the mozimo role.
|
||||
#
|
||||
# Quadlet units are not read by systemd directly: the podman user generator
|
||||
# turns them into services on daemon-reload, which the role does explicitly
|
||||
# after installing the unit. The service is "generated" (is-enabled reports
|
||||
# generated, not enabled) and is pulled in by default.target through the unit's
|
||||
# [Install] section, so there is nothing to enable.
|
||||
- name: Restart Mozimo
|
||||
ansible.builtin.systemd_service:
|
||||
name: "{{ title }}.service"
|
||||
state: restarted
|
||||
scope: user
|
||||
# check mode cannot generate the unit from a file it did not write, so a
|
||||
# rehearsal reports the change instead of failing on a missing service
|
||||
when: not ansible_check_mode
|
||||
listen: "Restart Mozimo"
|
||||
@@ -1,40 +1,43 @@
|
||||
---
|
||||
# Tasks for docker role
|
||||
- name: Log in to Docker registry
|
||||
docker_login:
|
||||
registry: "{{ registry }}"
|
||||
username: "{{ username }}"
|
||||
password: "{{ password }}"
|
||||
# The mozimo service itself: registry credentials, the pinned image, and the
|
||||
# Quadlet unit that systemd turns into a user service.
|
||||
#
|
||||
# There is one unit, not two: the image's entrypoint runs the SSR server and
|
||||
# PocketBase side by side, so the pair restart together.
|
||||
- name: Check the rootless podman installation
|
||||
ansible.builtin.command: podman --version
|
||||
changed_when: false
|
||||
|
||||
- name: Pull Mozimo image
|
||||
docker_image:
|
||||
name: "{{ docker_image }}"
|
||||
source: pull
|
||||
state: present
|
||||
force_source: yes
|
||||
- name: Log in to the registry as the podman user
|
||||
ansible.builtin.command:
|
||||
cmd: "podman login --username {{ username }} --password-stdin {{ registry }}"
|
||||
stdin: "{{ password }}"
|
||||
no_log: true
|
||||
changed_when: false
|
||||
|
||||
- name: Ensure PocketBase data directory exists
|
||||
file:
|
||||
path: "{{ host_directory }}/pb_data"
|
||||
state: directory
|
||||
owner: "1000"
|
||||
group: "1000"
|
||||
- name: Pull the image for this revision
|
||||
ansible.builtin.command: "podman pull {{ image }}"
|
||||
# the pull is a means to an end; the restart handler reports the real change
|
||||
changed_when: false
|
||||
|
||||
- name: Create Mozimo container
|
||||
docker_container:
|
||||
name: "{{ docker_container }}"
|
||||
image: "{{ docker_image }}"
|
||||
state: started
|
||||
restart_policy: "unless-stopped"
|
||||
env_file: "{{ host_directory }}/.env"
|
||||
volumes:
|
||||
- "{{ host_directory }}/pb_data:/app/pb_data"
|
||||
networks:
|
||||
- name: "{{ docker_network }}"
|
||||
- name: Install the mozimo Quadlet unit
|
||||
ansible.builtin.template:
|
||||
src: mozimo.container.j2
|
||||
dest: "{{ quadlet_dir }}/{{ title }}.container"
|
||||
mode: "0644"
|
||||
notify: Restart Mozimo
|
||||
|
||||
# Explicit, and before the handlers run: the unit does not exist as a systemd
|
||||
# service until the podman user generator has seen it, so generating it here
|
||||
# means the restart handler has something to restart on a first deploy.
|
||||
- name: Generate the user service from the Quadlet unit
|
||||
ansible.builtin.systemd_service:
|
||||
daemon_reload: true
|
||||
scope: user
|
||||
|
||||
- name: Prune old mozimo images, keep newest 3 (opt-in via ./deploy.sh --prune)
|
||||
ansible.builtin.shell: >
|
||||
docker images "registry.tanshu.com/mozimo" --format "{{.ID}}" | tail -n +4 | xargs -r docker rmi
|
||||
podman images -q {{ registry }}/{{ title }} | tail -n +4 | xargs -r podman rmi
|
||||
when: mozimo_image_prune | default(false) | bool
|
||||
failed_when: false
|
||||
changed_when: true
|
||||
@@ -0,0 +1,38 @@
|
||||
# Mozimo — rootless Podman Quadlet unit.
|
||||
# Managed by Ansible. Do not edit by hand.
|
||||
#
|
||||
# One unit runs the whole service: the image's entrypoint starts the TanStack
|
||||
# Start SSR server and PocketBase side by side, so they restart together.
|
||||
#
|
||||
# Nothing is published on the host. The shared Caddy joins
|
||||
# {{ network_quadlet }}.network and proxies to {{ app_container }}:{{ www_port }}
|
||||
# and :{{ cms_port }} by container name.
|
||||
#
|
||||
# Data lives at {{ host_directory }}/pb_data and survives redeploys: the
|
||||
# PocketBase database and files, the minted Shopify token, and the last-good
|
||||
# catalogue cache all sit there.
|
||||
|
||||
[Unit]
|
||||
Description=Mozimo commerce (TanStack Start + PocketBase)
|
||||
Documentation=https://git.tanshu.com/tanshu/mozimo.in
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Container]
|
||||
ContainerName={{ app_container }}
|
||||
Image={{ image }}
|
||||
|
||||
# The image's process runs as uid 1000 (node). Mapping that uid onto the host
|
||||
# user keeps pb_data owned by {{ podman_user }} rather than by a subuid, so
|
||||
# rsync, tar and backups stay simple.
|
||||
UserNS=keep-id:uid=1000,gid=1000
|
||||
|
||||
EnvironmentFile={{ host_directory }}/.env
|
||||
Volume={{ host_directory }}/pb_data:/app/pb_data
|
||||
Network={{ network_quadlet }}.network
|
||||
|
||||
[Service]
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
Reference in new issue
Block a user