Model the build on mozimo and target emily
- Makefile: PLATFORMS, tags :<commit> and :main, build-check with --pull. - deploy.sh: push, guards, git pull --ff-only, optional --prune, image_tag. - Replace .containerignore with .dockerignore. - Add the opt-in image prune to the hops role. - Tag the Caddyfile task so --skip-tags caddy works before DNS cutover. - Trim comments and rewrite build and deploy text in plain prose.
This commit is contained in:
1 parent
7366596a87
commit
a7c9324170
14 files changed
+194
-357
No files matched your search
@@ -4,7 +4,7 @@ A modern, full-stack craft microbrewery web application built with **TanStack St
|
||||
|
||||
---
|
||||
|
||||
## Architecture Overview
|
||||
## Architecture overview
|
||||
|
||||
```
|
||||
hops/
|
||||
@@ -36,7 +36,7 @@ hops/
|
||||
│ └── package.json
|
||||
├── ansible/ # Deployment playbook (roles: register, hops, caddy)
|
||||
├── Dockerfile # Multi-stage production container build
|
||||
├── .containerignore # Build-context excludes (Podman/Buildah)
|
||||
├── .dockerignore # Build-context excludes (podman and Docker)
|
||||
├── .gitignore # Git repository ignore rules
|
||||
├── docker-entrypoint.sh # Process supervisor running PB & Nitro
|
||||
├── Makefile # Podman build targets (build-production / build-check)
|
||||
@@ -47,9 +47,9 @@ hops/
|
||||
|
||||
---
|
||||
|
||||
## Running Locally
|
||||
## Running locally
|
||||
|
||||
### 1. Start Both Frontend & Backend Concurrently
|
||||
### 1. Start the frontend and backend together
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
@@ -79,32 +79,36 @@ npm run dev:frontend
|
||||
|
||||
## Deploying
|
||||
|
||||
The image is built with **Podman/Buildah** (not Docker) from the committed tree
|
||||
(`git archive HEAD`) and tagged with its **commit id**, so what runs on the host
|
||||
is exactly what git knows about. `deploy.sh` pushes the commit first, refuses a
|
||||
dirty tracked tree and requires `HEAD == origin/main`. Log in to the registry
|
||||
once per machine: `podman login registry.tanshu.com`.
|
||||
`deploy.sh` pushes the current branch, refuses a dirty tracked tree, and
|
||||
requires `HEAD == origin/main`. It builds that commit with podman and runs the
|
||||
Ansible play on emily. Log in to the registry once per machine:
|
||||
`podman login registry.tanshu.com`.
|
||||
|
||||
```bash
|
||||
./deploy.sh # push, build <commit>, deploy it to emily
|
||||
./deploy.sh --no-push # skip the push (you already pushed)
|
||||
./deploy.sh --force # skip the guards
|
||||
./deploy.sh # deploy HEAD
|
||||
./deploy.sh --prune # also drop old images on emily, keep 3
|
||||
./deploy.sh --force # skip the guards
|
||||
```
|
||||
|
||||
Or run the pieces yourself:
|
||||
The image is tagged with its commit id and `:main`. Rollback runs the same play
|
||||
with an older `image_tag`:
|
||||
|
||||
```bash
|
||||
make build-production # podman build HEAD, push :<commit> + :latest
|
||||
make build-check # compile check from HEAD, no push
|
||||
make build-production # podman build HEAD, push :<commit> and :main
|
||||
make build-check # build from HEAD, push nothing
|
||||
|
||||
cd ansible && ansible-playbook -u tanshu playbook.yml -e "image_tag=$(git rev-parse --short HEAD)"
|
||||
cd ansible && ansible-playbook playbook.yml -e "image_tag=$(git rev-parse --short HEAD)"
|
||||
```
|
||||
|
||||
The image is built for **linux/arm64** by default because `emily` is aarch64.
|
||||
Podman has no built-in QEMU, so cross-building from an x86_64 controller needs
|
||||
binfmt there (`qemu-user-binfmt`); override with `PLATFORM=linux/amd64`.
|
||||
The image builds for `linux/arm64` by default because emily is aarch64. Podman
|
||||
has no built-in QEMU, so cross-building from an x86_64 controller needs binfmt
|
||||
there (`qemu-user-static`). Override with `PLATFORMS=linux/amd64`:
|
||||
|
||||
To run the production image locally instead:
|
||||
```bash
|
||||
make build-production PLATFORMS=linux/amd64
|
||||
```
|
||||
|
||||
To run the production image locally:
|
||||
|
||||
```bash
|
||||
podman build -t localhost/hops:dev .
|
||||
@@ -115,55 +119,51 @@ podman run --rm -p 3000:3000 -p 8090:8090 \
|
||||
|
||||
### Ansible deployment
|
||||
|
||||
Requirements on the controller: `ansible` (no collections — the play uses
|
||||
built-in modules plus the `podman` / `systemctl --user` CLI). The target host
|
||||
must already run the fleet's shared Caddy **rootless Quadlet** setup: the
|
||||
`caddy/playbook.yml` from the `ansible-playbooks` repo owns the Caddy unit, the
|
||||
rootless prerequisites (Podman, `loginctl enable-linger`, the low-port sysctl)
|
||||
and the Caddyfile this play appends to.
|
||||
The controller needs `ansible`. The target host needs the shared Caddy rootless
|
||||
Quadlet setup from the `ansible-playbooks` repo. That playbook owns the Caddy
|
||||
unit, the rootless prerequisites, and the Caddyfile this play appends to.
|
||||
|
||||
The play targets the inventory host `emily` (present in `/etc/ansible/hosts`),
|
||||
connects as the unprivileged container user and **does not `become`**: the
|
||||
Quadlet units belong to that user, and the SSH connection's PAM session is what
|
||||
makes `systemctl --user` work.
|
||||
The play targets `emily`, connects as the unprivileged container user, and does
|
||||
not use `become`. The Quadlet units belong to that user, and the SSH session's
|
||||
PAM context makes `systemctl --user` work.
|
||||
|
||||
```bash
|
||||
cd ansible
|
||||
ansible-playbook -u tanshu playbook.yml -e "image_tag=<commit>"
|
||||
ansible-playbook playbook.yml -e "image_tag=<commit>"
|
||||
```
|
||||
|
||||
All deployment variables live in `ansible/vars/default.yml` (registry, image
|
||||
tag, domains, PocketBase admin credentials). The play:
|
||||
Skip the Caddyfile update until DNS points at emily. Otherwise Caddy retries
|
||||
certificate issuance for a name that still resolves to monoco:
|
||||
|
||||
1. Writes `~/.config/containers/systemd/hops.network`
|
||||
(`NetworkName=hops_net`) and a drop-in that attaches the shared Caddy unit to
|
||||
it, so Caddy can resolve `hops` by container name.
|
||||
2. Logs in to the private registry, pulls `registry.tanshu.com/hops:{image_tag}`
|
||||
(rollback is the same play with an older commit), writes
|
||||
`~/podman-data/hops/.env`, installs
|
||||
`~/.config/containers/systemd/hops.container`, starts it as a **systemd user
|
||||
service** and waits for the image's healthcheck (PocketBase `/api/health` +
|
||||
the site).
|
||||
3. Appends the site blocks to the shared Caddyfile
|
||||
(`~/podman-data/caddy/conf/Caddyfile`) and reloads Caddy:
|
||||
```bash
|
||||
ansible-playbook playbook.yml -e "image_tag=<commit>" --skip-tags caddy
|
||||
```
|
||||
|
||||
- `www.hopsngrains.com` (+ apex `hopsngrains.com`) — frontend on port 3000,
|
||||
PocketBase API/admin proxied under `/api/*` and `/_/*`
|
||||
- `admin.hopsngrains.com` — redirects to the PocketBase admin UI
|
||||
Deployment variables live in `ansible/vars/default.yml`. The play:
|
||||
|
||||
Nothing is published on the host; Caddy reaches the container over `hops_net`.
|
||||
1. Writes `~/.config/containers/systemd/hops.network` with `NetworkName=hops_net`,
|
||||
plus a drop-in that attaches the shared Caddy unit to it. Caddy can then
|
||||
resolve `hops` by container name.
|
||||
2. Logs in to the registry, pulls `registry.tanshu.com/hops:{image_tag}`, writes
|
||||
`~/podman-data/hops/.env`, installs `hops.container`, starts it as a systemd
|
||||
user service, and waits for the healthcheck.
|
||||
3. Appends the site blocks to `~/podman-data/caddy/conf/Caddyfile` and reloads
|
||||
Caddy. `www.hopsngrains.com` and the apex serve the frontend on port 3000 and
|
||||
proxy `/api/*` and `/_/*` to PocketBase. `admin.hopsngrains.com` redirects to
|
||||
the PocketBase admin UI.
|
||||
|
||||
Nothing is published on the host. Caddy reaches the container over `hops_net`.
|
||||
The container runs as uid 0, so the unit maps it onto the podman user with
|
||||
`UserNS=keep-id:uid=0,gid=0`: `~/podman-data/hops` stays owned by `tanshu`, and
|
||||
PocketBase's files need no `podman unshare` to inspect or back up.
|
||||
`UserNS=keep-id:uid=0,gid=0`. Files under `~/podman-data/hops` stay owned by
|
||||
`tanshu`, and PocketBase's files need no `podman unshare` to read or back up.
|
||||
|
||||
DNS records for all three hosts must point at `emily`; Caddy obtains TLS
|
||||
certificates automatically on first request. This deploy starts from empty
|
||||
data — the schema migration in `backend/pb_migrations/` is applied on first
|
||||
start, so the seed beers and food are recreated automatically.
|
||||
DNS for all three hosts must point at emily before Caddy can issue certificates.
|
||||
The first deploy starts with empty data. PocketBase applies the schema migration
|
||||
in `backend/pb_migrations/` on first boot and recreates the seed beers and food.
|
||||
|
||||
---
|
||||
|
||||
## Features & Pages
|
||||
## Features and pages
|
||||
|
||||
| Route | Name | Highlights |
|
||||
|---|---|---|
|
||||
|
||||
Reference in new issue
Block a user