Deploy: push before building, and tag images with the commit
deploy.sh no longer needs a manual push before it, and no made-up tag: - It pushes the current branch, refuses a dirty tracked tree, and requires HEAD == origin/main before anything is built, so the image can only be built for a commit the remote actually has. --force and --no-push skip the guards. - The image is tagged :<commit> as well as :latest, and ansible takes -e image_tag=<commit>, so a deploy pins the exact revision; rollback is the same play with an older commit. - Makefile builds `git archive HEAD` instead of cloning the remote (the push guard makes the local commit the remote state) and defaults PLATFORM to linux/arm64, because emily is aarch64 — the same default mozimo uses. Drop build-check-local; build-check already builds the committed tree.
This commit is contained in:
1 parent
9620847108
commit
1c78fc5103
4 files changed
+113
-84
No files matched your search
@@ -1,63 +1,42 @@
|
||||
# The Great Bear — container image builds.
|
||||
#
|
||||
# Built with Podman/Buildah, not Docker. The image is always built from a fresh
|
||||
# clone of the git remote, so it matches a pushed commit; commit and push before
|
||||
# deploying. Registry credentials: `podman login registry.tanshu.com` once per
|
||||
# machine (nothing here prompts).
|
||||
# Built with Podman/Buildah, not Docker, from the committed tree
|
||||
# (`git archive HEAD`) and tagged with that commit, so a deploy ships exactly
|
||||
# what git knows about. deploy.sh pushes first and refuses a dirty tree.
|
||||
|
||||
COMMIT ?= $(shell git rev-parse --short HEAD)
|
||||
|
||||
# The deploy host (emily) is aarch64, so that is the default target: the image
|
||||
# is built for the machine it runs on, not the one running make. Podman carries
|
||||
# no built-in QEMU (unlike docker buildx), so a foreign platform needs binfmt
|
||||
# handlers on this host — qemu-user-binfmt is installed here. Override with e.g.
|
||||
# `make build-production PLATFORM=linux/amd64`.
|
||||
PLATFORM ?= linux/arm64
|
||||
|
||||
IMAGE ?= registry.tanshu.com/tanshu/greatbear
|
||||
REPO ?= git@git.tanshu.com:tanshu/greatbear.git
|
||||
TAG ?= latest
|
||||
|
||||
# Target platform. Defaults to the architecture of the machine running make,
|
||||
# which is what a native `podman build` would use anyway. Only cross-build when
|
||||
# the deploy host differs, e.g.:
|
||||
#
|
||||
# make build-production PLATFORM=linux/arm64
|
||||
#
|
||||
# Cross-building needs QEMU binfmt registered on the controller
|
||||
# (qemu-user-static), the same requirement `docker buildx --platform` had.
|
||||
PLATFORM ?= linux/$(shell uname -m | sed -e 's/x86_64/amd64/' -e 's/aarch64/arm64/' -e 's/armv7l/arm/')
|
||||
|
||||
.PHONY: build-production
|
||||
build-production: ## Build the image with podman from the git remote and push it
|
||||
@set -euo pipefail; \
|
||||
ctx="$$(mktemp -d)"; \
|
||||
trap 'rm -rf "$$ctx"' EXIT; \
|
||||
echo "==> cloning $(REPO)"; \
|
||||
git clone --quiet --depth 1 "$(REPO)" "$$ctx/src"; \
|
||||
echo "==> podman build --platform $(PLATFORM) -t $(IMAGE):$(TAG)"; \
|
||||
podman build --pull=always --platform "$(PLATFORM)" \
|
||||
-t "$(IMAGE):$(TAG)" \
|
||||
-f "$$ctx/src/Dockerfile" "$$ctx/src"; \
|
||||
echo "==> podman push $(IMAGE):$(TAG)"; \
|
||||
podman push "$(IMAGE):$(TAG)"; \
|
||||
if [ "$(TAG)" != "latest" ]; then \
|
||||
echo "==> podman push $(IMAGE):latest"; \
|
||||
podman tag "$(IMAGE):$(TAG)" "$(IMAGE):latest"; \
|
||||
podman push "$(IMAGE):latest"; \
|
||||
fi
|
||||
build-production: ## Build HEAD with podman, tag :$(COMMIT) + :latest, and push
|
||||
@echo "==> podman build --platform $(PLATFORM) → $(IMAGE):$(COMMIT) + :latest"
|
||||
@git archive --format=tar HEAD | podman build \
|
||||
--platform $(PLATFORM) \
|
||||
--tag $(IMAGE):$(COMMIT) \
|
||||
--tag $(IMAGE):latest \
|
||||
--pull=always \
|
||||
- || { \
|
||||
echo "✗ podman build failed."; \
|
||||
echo " Cross-platform builds need binfmt on this host (podman has no built-in QEMU):"; \
|
||||
echo " sudo apt install qemu-user-binfmt # or qemu-user-static"; \
|
||||
exit 1; \
|
||||
}
|
||||
podman push $(IMAGE):$(COMMIT)
|
||||
podman push $(IMAGE):latest
|
||||
|
||||
.PHONY: build-check
|
||||
build-check: ## Build from the git remote without pushing (compile check)
|
||||
@set -euo pipefail; \
|
||||
ctx="$$(mktemp -d)"; \
|
||||
trap 'rm -rf "$$ctx"' EXIT; \
|
||||
echo "==> cloning $(REPO)"; \
|
||||
git clone --quiet --depth 1 "$(REPO)" "$$ctx/src"; \
|
||||
echo "==> podman build --platform $(PLATFORM) greatbear:test"; \
|
||||
podman build --pull=always --platform "$(PLATFORM)" \
|
||||
-t localhost/greatbear:test \
|
||||
-f "$$ctx/src/Dockerfile" "$$ctx/src"
|
||||
|
||||
.PHONY: build-check-local
|
||||
build-check-local: ## Build the committed local tree (git archive) without pushing
|
||||
@set -euo pipefail; \
|
||||
ctx="$$(mktemp -d)"; \
|
||||
trap 'rm -rf "$$ctx"' EXIT; \
|
||||
mkdir -p "$$ctx/src"; \
|
||||
git archive --format=tar HEAD | tar -x -C "$$ctx/src"; \
|
||||
echo "==> podman build --platform $(PLATFORM) greatbear:test (local HEAD)"; \
|
||||
podman build --pull=always --platform "$(PLATFORM)" \
|
||||
-t localhost/greatbear:test \
|
||||
-f "$$ctx/src/Dockerfile" "$$ctx/src"
|
||||
build-check: ## Compile check from HEAD with podman, nothing pushed
|
||||
@echo "==> podman build --platform $(PLATFORM) (no push)"
|
||||
git archive --format=tar HEAD | podman build \
|
||||
--platform $(PLATFORM) \
|
||||
--tag localhost/greatbear:check \
|
||||
--pull=always \
|
||||
-
|
||||
@@ -88,33 +88,31 @@ podman run --rm -p 3000:3000 -p 8090:8090 \
|
||||
|
||||
## Deploying
|
||||
|
||||
The image is built with **Podman/Buildah** (not Docker) and always from the
|
||||
**committed state of the git remote** — commit and push before deploying. The
|
||||
Makefile never prompts, so log in to the registry once per machine:
|
||||
`podman login registry.tanshu.com`.
|
||||
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`.
|
||||
|
||||
```bash
|
||||
./deploy.sh # build + push :latest, then deploy that tag via Ansible
|
||||
./deploy.sh v1.2.3 # same, with an explicit version tag
|
||||
./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
|
||||
```
|
||||
|
||||
Or run the pieces yourself:
|
||||
|
||||
```bash
|
||||
make build-production TAG=v1.2.3 # podman build + push from the git remote
|
||||
make build-check # build from the git remote, no push
|
||||
make build-check-local # build the committed local tree (git archive), no push
|
||||
make build-production # podman build HEAD, push :<commit> + :latest
|
||||
make build-check # compile check from HEAD, no push
|
||||
|
||||
cd ansible && ansible-playbook -u tanshu playbook.yml -e "tag=v1.2.3"
|
||||
cd ansible && ansible-playbook -u tanshu playbook.yml -e "image_tag=$(git rev-parse --short HEAD)"
|
||||
```
|
||||
|
||||
Images are built for the controller's architecture by default. Only cross-build
|
||||
when the deploy host differs, and register QEMU binfmt first
|
||||
(`qemu-user-static`), the same requirement `docker buildx --platform` had:
|
||||
|
||||
```bash
|
||||
make build-production PLATFORM=linux/arm64
|
||||
```
|
||||
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`, already installed on this machine); override
|
||||
with `PLATFORM=linux/amd64`.
|
||||
|
||||
### Ansible deployment
|
||||
|
||||
@@ -132,17 +130,18 @@ makes `systemctl --user` work.
|
||||
|
||||
```bash
|
||||
cd ansible
|
||||
ansible-playbook -u tanshu playbook.yml -e "tag=v1.2.3"
|
||||
ansible-playbook -u tanshu playbook.yml -e "image_tag=<commit>"
|
||||
```
|
||||
|
||||
All deployment variables live in `ansible/vars/default.yml` (registry, tag,
|
||||
domains, PocketBase admin credentials). The play:
|
||||
All deployment variables live in `ansible/vars/default.yml` (registry, image
|
||||
tag, domains, PocketBase admin credentials). The play:
|
||||
|
||||
1. Writes `~/.config/containers/systemd/greatbear.network`
|
||||
(`NetworkName=greatbear_net`) and a drop-in that attaches the shared Caddy
|
||||
unit to it, so Caddy can resolve `greatbear` by container name.
|
||||
2. Logs in to the private registry, pulls
|
||||
`registry.tanshu.com/tanshu/greatbear:{tag}`, writes
|
||||
`registry.tanshu.com/tanshu/greatbear:{image_tag}` (rollback is the same play
|
||||
with an older commit), writes
|
||||
`~/podman-data/greatbear/.env`, installs
|
||||
`~/.config/containers/systemd/greatbear.container`, starts it as a **systemd
|
||||
user service** and waits for the image's healthcheck (PocketBase
|
||||
|
||||
@@ -19,7 +19,9 @@ registry_password: ff28a01f00c0f39315d94cd9dcb1e554968dba25676a8ea5f2be34e96a9a0
|
||||
|
||||
image_name: "{{ registry }}/tanshu/greatbear"
|
||||
tag: latest
|
||||
image: "{{ image_name }}:{{ tag }}"
|
||||
# deploy.sh passes -e image_tag=<commit>; `tag` is the fallback when running the
|
||||
# play by hand. Rollback = -e image_tag=<older-commit>.
|
||||
image: "{{ image_name }}:{{ image_tag | default(tag) }}"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# The Great Bear
|
||||
|
||||
@@ -1,15 +1,63 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# The Great Bear deploy — push the current commit, build it with podman, and
|
||||
# install the Quadlet unit on emily with ansible.
|
||||
#
|
||||
# The image is built 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 deploy HEAD (tracked tree must be clean and pushed)
|
||||
# ./deploy.sh --force skip the guards (know what you are doing)
|
||||
# ./deploy.sh --no-push skip the push, if you already pushed
|
||||
#
|
||||
# Log in to the registry once beforehand: podman login registry.tanshu.com
|
||||
set -euo pipefail
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||
|
||||
parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" || exit ; pwd -P )
|
||||
cd "$parent_path" || exit
|
||||
BRANCH="${BRANCH:-main}"
|
||||
|
||||
FORCE=false
|
||||
NO_PUSH=false
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--force) FORCE=true ;;
|
||||
--no-push) NO_PUSH=true ;;
|
||||
*)
|
||||
echo "Usage: $0 [--force] [--no-push]" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# The build takes HEAD, so push first: otherwise the registry collects an image
|
||||
# for a commit that exists only on this machine.
|
||||
if [ "$NO_PUSH" != true ]; then
|
||||
echo "==> pushing"
|
||||
git push
|
||||
fi
|
||||
|
||||
# Guards. Only tracked-file state matters — untracked and ignored files never
|
||||
# enter a `git archive HEAD` build. HEAD must match origin/$BRANCH so the commit
|
||||
# the image is tagged with is one the remote actually has.
|
||||
if [ "$FORCE" != true ]; then
|
||||
if [ -n "$(git status --porcelain --untracked-files=no)" ]; then
|
||||
echo "x tracked files modified - commit or stash first (or --force)" >&2
|
||||
exit 1
|
||||
fi
|
||||
git fetch origin --quiet
|
||||
if [ "$(git rev-parse HEAD)" != "$(git rev-parse "origin/$BRANCH")" ]; then
|
||||
echo "x HEAD != origin/$BRANCH - push or pull first (or --force)" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
TAG=$(git rev-parse --short HEAD)
|
||||
echo "==> deploying commit $TAG"
|
||||
|
||||
echo "========================================================"
|
||||
echo " Building & Uploading Container Image (Podman)"
|
||||
echo "========================================================"
|
||||
current_version="${1:-latest}"
|
||||
make build-production TAG="$current_version"
|
||||
|
||||
make build-production COMMIT="$TAG"
|
||||
|
||||
echo ""
|
||||
echo "========================================================"
|
||||
@@ -19,5 +67,6 @@ echo "========================================================"
|
||||
# connecting user, so it must connect as that user and must NOT become root.
|
||||
# `-u` is the account that owns ~/.config/containers/systemd on the target.
|
||||
deploy_user="${ANSIBLE_USER:-tanshu}"
|
||||
cd "$parent_path/ansible" || exit
|
||||
ansible-playbook -u "$deploy_user" playbook.yml -e "tag=$current_version"
|
||||
cd ansible || exit
|
||||
# image_tag pins the exact commit; rollback is -e image_tag=<older-commit>.
|
||||
ansible-playbook -u "$deploy_user" playbook.yml -e "image_tag=$TAG"
|
||||
Reference in new issue
Block a user