Rebuild ansible on the project deploy template

- playbook.yml (hosts: monoco) + vars/default.yml, requirements.yml
- roles: network (shared docker network), greatbear (registry pull,
  bind mount /var/lib/greatbear/pb_data, .env upload, health wait),
  caddy (blockinfile snippet into the shared Caddyfile + docker exec reload)
- image honors PB_DATA_DIR and runs as root to match the bind-mount pattern
- frontend on www.greatbear.in (+apex) with /api/* and /_* proxied to
  PocketBase; admin.greatbear.in redirects to the admin UI
This commit is contained in:
2026-09-03 10:14:11 +05:30
parent c03b9c582b
commit 757ec8aee2
23 changed files with 200 additions and 205 deletions
-9
View File
@@ -1,9 +0,0 @@
[defaults]
inventory = inventory/hosts.yml
roles_path = roles
host_key_checking = True
retry_files_enabled = False
interpreter_python = auto_silent
[ssh_connection]
pipelining = True
+7
View File
@@ -0,0 +1,7 @@
NODE_ENV=production
PORT=3000
HOST=0.0.0.0
PB_DATA_DIR=/app/pb_data
PB_URL=http://127.0.0.1:8090
PB_SUPERUSER_EMAIL={{ pb_admin_email }}
PB_SUPERUSER_PASSWORD={{ pb_admin_password }}
+22
View File
@@ -0,0 +1,22 @@
{{ host }}{% if apex_host is defined and apex_host %}, {{ apex_host }}{% endif %} {
# PocketBase Admin UI and REST API
redir /_ /_/
handle /_/* {
reverse_proxy {{ container_name }}:8090
}
handle /api/* {
reverse_proxy {{ container_name }}:8090
}
# TanStack Start Frontend Application
handle {
reverse_proxy {{ container_name }}:3000
}
}
{% if admin_host is defined and admin_host %}
{{ admin_host }} {
redir / /_/
reverse_proxy {{ container_name }}:8090
}
{% endif %}
-31
View File
@@ -1,31 +0,0 @@
---
# Deployment variables for The Great Bear.
# NOTE: This file contains credentials. For anything beyond a private homelab
# repo, encrypt it with: ansible-vault encrypt group_vars/greatbear/vars.yml
# --- Domains (Caddy provisions TLS for both automatically) ---
site_domain: www.greatbear.in
admin_domain: admin.greatbear.in
# --- Container image ---
registry_url: registry.tanshu.com
registry_username: ta-registry
registry_password: ff28a01f00c0f39315d94cd9dcb1e554968dba25676a8ea5f2be34e96a9a099f
app_image: registry.tanshu.com/tanshu/greatbear
app_image_tag: latest
# --- Container runtime ---
app_container_name: greatbear
app_volume_name: greatbear_pb_data
# Ports are bound to loopback only; Caddy proxies public traffic to them.
app_site_publish: 127.0.0.1:3000:3000
app_admin_publish: 127.0.0.1:8090:8090
# --- PocketBase initial admin (idempotent bootstrap on container start) ---
pb_superuser_email: admin@greatbear.in
pb_superuser_password: GreatBear!Admin2024
# --- Caddy ---
caddy_config_dir: /etc/caddy/caddy.d
caddyfile_path: /etc/caddy/Caddyfile
caddy_service: caddy
-9
View File
@@ -1,9 +0,0 @@
all:
children:
greatbear:
hosts:
www.greatbear.in:
ansible_user: root
# ansible_ssh_private_key_file: ~/.ssh/id_ed25519
# Override ansible_user if you log in as a non-root sudo user:
# ansible_user: ubuntu
+11
View File
@@ -0,0 +1,11 @@
---
- name: Deploy The Great Bear Microbrewery
hosts: monoco
become: true
vars_files:
- vars/default.yml
roles:
- network
- greatbear
- caddy
+3
View File
@@ -0,0 +1,3 @@
---
collections:
- name: community.docker
-58
View File
@@ -1,58 +0,0 @@
---
# Pull the image from the private registry and (re)create the app container.
- name: Log in to the private registry
community.docker.docker_login:
registry_url: "{{ registry_url }}"
username: "{{ registry_username }}"
password: "{{ registry_password }}"
- name: Pull the application image
community.docker.docker_image:
name: "{{ app_image }}"
tag: "{{ app_image_tag }}"
source: pull
force_source: true
- name: Run the application container
community.docker.docker_container:
name: "{{ app_container_name }}"
image: "{{ app_image }}:{{ app_image_tag }}"
state: started
recreate: true
restart_policy: unless-stopped
published_ports:
- "{{ app_site_publish }}"
- "{{ app_admin_publish }}"
volumes:
- "{{ app_volume_name }}:/data"
env:
PORT: "3000"
PB_URL: "http://127.0.0.1:8090"
PB_SUPERUSER_EMAIL: "{{ pb_superuser_email }}"
PB_SUPERUSER_PASSWORD: "{{ pb_superuser_password }}"
comparisons:
image: ignore # recreate: true already handles image updates
container_default_behavior: compatibility
- name: Wait for PocketBase to become healthy
ansible.builtin.uri:
url: http://127.0.0.1:8090/api/health
status_code: 200
register: pb_health
retries: 30
delay: 5
until: pb_health.status == 200
- name: Wait for the site to respond
ansible.builtin.uri:
url: http://127.0.0.1:3000/
status_code: 200
register: site_health
retries: 30
delay: 5
until: site_health.status == 200
- name: Log out of the private registry
community.docker.docker_login:
registry_url: "{{ registry_url }}"
state: absent
+3
View File
@@ -0,0 +1,3 @@
---
caddy_container: caddy
caddy_caddyfile_path: /var/lib/caddy/conf/Caddyfile
+5
View File
@@ -0,0 +1,5 @@
---
- name: Reload Caddy configuration
ansible.builtin.command: "docker exec -w /etc/caddy {{ caddy_container }} caddy reload"
listen: "Reload Caddy"
changed_when: true
-5
View File
@@ -1,5 +0,0 @@
---
- name: Reload Caddy
ansible.builtin.systemd:
name: "{{ caddy_service }}"
state: reloaded
+26
View File
@@ -0,0 +1,26 @@
---
- name: Read snippet from template file
ansible.builtin.set_fact:
caddy_snippet_block: "{{ lookup('template', 'files/Caddyfile.j2') }}"
- name: Read current Caddyfile
ansible.builtin.slurp:
path: "{{ caddy_caddyfile_path }}"
register: caddy_caddyfile_raw
- name: Decode Caddyfile content
ansible.builtin.set_fact:
caddy_caddyfile_content: "{{ caddy_caddyfile_raw['content'] | b64decode }}"
- name: Check if snippet already exists
ansible.builtin.set_fact:
caddy_snippet_present: "{{ caddy_snippet_block in caddy_caddyfile_content }}"
- name: Add or update snippet in Caddyfile
ansible.builtin.blockinfile:
path: "{{ caddy_caddyfile_path }}"
marker: "# {mark} Ansible managed Caddy snippet for {{ host }}"
block: "{{ caddy_snippet_block }}"
create: true
mode: "0644"
notify: Reload Caddy configuration
-47
View File
@@ -1,47 +0,0 @@
---
# Manage Caddy entries for the site and the PocketBase admin/API host.
- name: Ensure Caddy config include directory exists
ansible.builtin.file:
path: "{{ caddy_config_dir }}"
state: directory
owner: root
group: root
mode: "0755"
- name: Check whether the main Caddyfile exists
ansible.builtin.stat:
path: "{{ caddyfile_path }}"
register: caddyfile_stat
- name: Ensure the main Caddyfile imports conf.d entries
ansible.builtin.lineinfile:
path: "{{ caddyfile_path }}"
line: "import {{ caddy_config_dir }}/*.conf"
insertafter: EOF
state: present
when: caddyfile_stat.stat.exists
notify: Reload Caddy
- name: Install Great Bear Caddy entries
ansible.builtin.template:
src: greatbear.conf.j2
dest: "{{ caddy_config_dir }}/greatbear.conf"
owner: root
group: root
mode: "0644"
notify: Reload Caddy
- name: Validate Caddy configuration
ansible.builtin.command:
cmd: "caddy validate --config {{ caddyfile_path }} --adapter caddyfile"
changed_when: false
when: caddyfile_stat.stat.exists
- name: Flush handlers (reload Caddy now)
ansible.builtin.meta: flush_handlers
- name: Ensure Caddy is enabled and running
ansible.builtin.systemd:
name: "{{ caddy_service }}"
state: started
enabled: true
@@ -1,12 +0,0 @@
# The Great Bear — managed by Ansible. Changes will be overwritten.
# Frontend (TanStack Start)
{{ site_domain }} {
encode zstd gzip
reverse_proxy 127.0.0.1:3000
}
# PocketBase API + admin UI (http://{{ admin_domain }}/_/)
{{ admin_domain }} {
encode zstd gzip
reverse_proxy 127.0.0.1:8090
}
+56
View File
@@ -0,0 +1,56 @@
---
- name: Log in to private Docker registry
community.docker.docker_login:
registry: "{{ registry }}"
username: "{{ username }}"
password: "{{ password }}"
- name: Pull The Great Bear image
community.docker.docker_image:
name: "{{ image_name }}"
source: pull
state: present
force_source: true
- name: Ensure Host Directory exists
ansible.builtin.file:
path: "{{ host_data_path }}"
state: directory
mode: "0755"
- name: Ensure PocketBase data directory exists
ansible.builtin.file:
path: "{{ host_data_path }}/pb_data"
state: directory
mode: "0755"
- name: Upload the .env file
ansible.builtin.template:
src: "files/.env.j2"
dest: "{{ host_data_path }}/.env"
mode: "0600"
- name: Create and run The Great Bear container
community.docker.docker_container:
name: "{{ container_name }}"
image: "{{ image_name }}"
state: started
restart_policy: "unless-stopped"
env_file: "{{ host_data_path }}/.env"
volumes:
- "{{ host_data_path }}/pb_data:/app/pb_data"
networks:
- name: "{{ docker_network }}"
- name: Wait for the application container to become healthy
community.docker.docker_container_info:
name: "{{ container_name }}"
register: app_info
until: app_info.container.State.Health.Status == "healthy"
retries: 30
delay: 5
- name: Log out of private Docker registry
community.docker.docker_login:
registry: "{{ registry }}"
state: absent
+8
View File
@@ -0,0 +1,8 @@
---
- name: Ensure Docker network exists
community.docker.docker_network:
name: "{{ docker_network }}"
state: present
connected:
- "{{ caddy_container }}"
appends: true
-9
View File
@@ -1,9 +0,0 @@
---
- name: Deploy The Great Bear (PocketBase + TanStack Start)
hosts: greatbear
become: true
roles:
- role: app
tags: [app, deploy]
- role: caddy
tags: [caddy, web]
+27
View File
@@ -0,0 +1,27 @@
---
registry: registry.tanshu.com
username: ta-registry
password: ff28a01f00c0f39315d94cd9dcb1e554968dba25676a8ea5f2be34e96a9a099f
tag: latest
image_name: "{{ registry }}/tanshu/greatbear:{{ tag }}"
app_name: greatbear
host_directory: "{{ app_name }}"
host_data_path: "/var/lib/{{ host_directory }}"
container_name: "{{ app_name }}"
docker_network: "{{ app_name }}_net"
# Public hosts for frontend & PocketBase routing
host: www.greatbear.in
apex_host: greatbear.in
admin_host: admin.greatbear.in
# Caddy configuration
caddy_container: caddy
caddy_caddyfile_path: /var/lib/caddy/conf/Caddyfile
# PocketBase Initial Superuser Credentials
pb_admin_email: admin@greatbear.in
pb_admin_password: GreatBear!Admin2024