deploy: join gitea netns instead of bridge (machine kernel lacks ip_tables for rootless netavark); pin image digests; push-to-create repo
This commit is contained in:
@@ -3,5 +3,5 @@
|
|||||||
# keeps the whole toolchain pinned and independent of host packages.
|
# keeps the whole toolchain pinned and independent of host packages.
|
||||||
# Built ONCE at install, never pulled again at runtime:
|
# Built ONCE at install, never pulled again at runtime:
|
||||||
# podman build -t localhost/ctao-portal-build:1 -f Containerfile.build .
|
# podman build -t localhost/ctao-portal-build:1 -f Containerfile.build .
|
||||||
FROM docker.io/library/node:24-alpine
|
FROM docker.io/library/node:24-alpine@sha256:a0b9bf06e4e6193cf7a0f58816cc935ff8c2a908f81e6f1a95432d679c54fbfd
|
||||||
RUN apk add --no-cache git
|
RUN apk add --no-cache git
|
||||||
|
|||||||
+5
-5
@@ -24,7 +24,7 @@ Uninstall restores the machine exactly (see bottom). No secrets in any file.
|
|||||||
|
|
||||||
1. **[W]** `mkdir -p ~/ctao-portal-demo/{gitea-data,gitea-config,releases,state,bin,config} ~/.config/containers/systemd ~/.config/systemd/user`
|
1. **[W]** `mkdir -p ~/ctao-portal-demo/{gitea-data,gitea-config,releases,state,bin,config} ~/.config/containers/systemd ~/.config/systemd/user`
|
||||||
2. **[W]** Copy files from this dir (scp from the Mac):
|
2. **[W]** Copy files from this dir (scp from the Mac):
|
||||||
- `ctao-demo-gitea.container`, `ctao-demo-web.container`, `ctao-demo.network` → `~/.config/containers/systemd/`
|
- `ctao-demo-gitea.container`, `ctao-demo-web.container` → `~/.config/containers/systemd/`
|
||||||
- `ctao-portal-build.service`, `ctao-portal-build.timer` → `~/.config/systemd/user/`
|
- `ctao-portal-build.service`, `ctao-portal-build.timer` → `~/.config/systemd/user/`
|
||||||
- `build.sh` → `~/ctao-portal-demo/bin/` (`chmod +x`)
|
- `build.sh` → `~/ctao-portal-demo/bin/` (`chmod +x`)
|
||||||
- `nginx.conf` → `~/ctao-portal-demo/config/`
|
- `nginx.conf` → `~/ctao-portal-demo/config/`
|
||||||
@@ -72,10 +72,8 @@ Uninstall restores the machine exactly (see bottom). No secrets in any file.
|
|||||||
systemctl --user disable --now ctao-portal-build.timer
|
systemctl --user disable --now ctao-portal-build.timer
|
||||||
systemctl --user stop ctao-demo-web ctao-demo-gitea
|
systemctl --user stop ctao-demo-web ctao-demo-gitea
|
||||||
rm ~/.config/containers/systemd/ctao-demo-*.container \
|
rm ~/.config/containers/systemd/ctao-demo-*.container \
|
||||||
~/.config/containers/systemd/ctao-demo.network \
|
|
||||||
~/.config/systemd/user/ctao-portal-build.{service,timer}
|
~/.config/systemd/user/ctao-portal-build.{service,timer}
|
||||||
systemctl --user daemon-reload
|
systemctl --user daemon-reload
|
||||||
podman network rm ctao-demo 2>/dev/null || true
|
|
||||||
podman rmi localhost/ctao-portal-build:1 docker.io/gitea/gitea:1.27-rootless docker.io/library/nginx:stable-alpine
|
podman rmi localhost/ctao-portal-build:1 docker.io/gitea/gitea:1.27-rootless docker.io/library/nginx:stable-alpine
|
||||||
rm -rf ~/ctao-portal-demo
|
rm -rf ~/ctao-portal-demo
|
||||||
```
|
```
|
||||||
@@ -90,7 +88,9 @@ rm -rf ~/ctao-portal-demo
|
|||||||
the build container is capped at 1 GB via `podman run --memory`. Watch the
|
the build container is capped at 1 GB via `podman run --memory`. Watch the
|
||||||
first `npm ci` + build in the journal — if it OOMs inside its cgroup
|
first `npm ci` + build in the journal — if it OOMs inside its cgroup
|
||||||
(contained, just retries), raise the cap.
|
(contained, just retries), raise the cap.
|
||||||
- Build isolation: build containers run on the `ctao-demo` bridge (see
|
- Build isolation: build containers join the Gitea container's network
|
||||||
`ctao-demo.network`) — repo/npm code cannot reach host loopback services.
|
namespace (`--network=container:ctao-demo-gitea`) — repo/npm code sees
|
||||||
|
Gitea on localhost:3000 but cannot reach host loopback services. (Rootless
|
||||||
|
netavark bridges don't work here: no `ip_tables` kernel module, no sudo.)
|
||||||
- Secrets inventory: Gitea admin password (typed interactively, lives only
|
- Secrets inventory: Gitea admin password (typed interactively, lives only
|
||||||
in Gitea's DB) — that's the complete list. Build/poll/serve use none.
|
in Gitea's DB) — that's the complete list. Build/poll/serve use none.
|
||||||
|
|||||||
+10
-8
@@ -12,10 +12,12 @@ GITEA_URL="${GITEA_URL:-http://localhost:3000}" # published by ctao-demo-gitea
|
|||||||
REPO="${REPO:-ctao/portal}" # owner/repo in Gitea
|
REPO="${REPO:-ctao/portal}" # owner/repo in Gitea
|
||||||
BRANCH="${BRANCH:-main}"
|
BRANCH="${BRANCH:-main}"
|
||||||
BUILD_IMAGE="${BUILD_IMAGE:-localhost/ctao-portal-build:1}"
|
BUILD_IMAGE="${BUILD_IMAGE:-localhost/ctao-portal-build:1}"
|
||||||
BUILD_NET="${BUILD_NET:-ctao-demo}" # bridge from ctao-demo.network
|
# The build joins the Gitea container's network namespace: localhost inside
|
||||||
# Clone URL as seen from INSIDE the build network (container-name DNS);
|
# the build = Gitea's loopback (port 3000), host loopback stays unreachable.
|
||||||
# the poll below uses $GITEA_URL because it runs on the host.
|
# (A netavark bridge would be equivalent, but rootless bridges need the
|
||||||
REPO_INTERNAL="${REPO_INTERNAL:-http://ctao-demo-gitea:3000/$REPO.git}"
|
# ip_tables kernel module, absent on the machine — pasta needs nothing.)
|
||||||
|
BUILD_NETNS="${BUILD_NETNS:-container:ctao-demo-gitea}"
|
||||||
|
REPO_INTERNAL="${REPO_INTERNAL:-http://localhost:3000/$REPO.git}"
|
||||||
KEEP="${KEEP:-3}" # released builds to retain
|
KEEP="${KEEP:-3}" # released builds to retain
|
||||||
mkdir -p "$BASE/repo" "$BASE/releases" "$BASE/state"
|
mkdir -p "$BASE/repo" "$BASE/releases" "$BASE/state"
|
||||||
|
|
||||||
@@ -35,11 +37,11 @@ t0=$(date +%s)
|
|||||||
|
|
||||||
# --- 2. Build in the ephemeral container (git + pinned node live there).
|
# --- 2. Build in the ephemeral container (git + pinned node live there).
|
||||||
# SECURITY: the container runs npm lifecycle scripts from the repo, so it is
|
# SECURITY: the container runs npm lifecycle scripts from the repo, so it is
|
||||||
# confined to the ctao-demo bridge — it reaches Gitea by container name and
|
# confined to Gitea's netns — it reaches Gitea on localhost:3000 and the
|
||||||
# the internet (for `npm ci` when the lockfile changed), but NOT the host's
|
# internet (for `npm ci` when the lockfile changed), but NOT the host's
|
||||||
# loopback services (code-server, Strapi). Never use --network=host here.
|
# loopback services (code-server). Never use --network=host here.
|
||||||
# node_modules and .deps-hash are untracked, so they survive checkouts.
|
# node_modules and .deps-hash are untracked, so they survive checkouts.
|
||||||
podman run --rm --network="$BUILD_NET" --memory=1g \
|
podman run --rm --network="$BUILD_NETNS" --memory=1g \
|
||||||
-e ASTRO_TELEMETRY_DISABLED=1 \
|
-e ASTRO_TELEMETRY_DISABLED=1 \
|
||||||
-e SHA="$sha" -e REPO_URL="$REPO_INTERNAL" -e BRANCH="$BRANCH" \
|
-e SHA="$sha" -e REPO_URL="$REPO_INTERNAL" -e BRANCH="$BRANCH" \
|
||||||
-v "$BASE/repo:/work/repo:z" \
|
-v "$BASE/repo:/work/repo:z" \
|
||||||
|
|||||||
@@ -11,16 +11,15 @@ After=network-online.target
|
|||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=ctao-demo-gitea
|
ContainerName=ctao-demo-gitea
|
||||||
# Same major as the local demo (1.27). Re-pin to the exact digest at install:
|
# Pinned by digest at install (2026-07-28); tag kept for readability.
|
||||||
# podman pull docker.io/gitea/gitea:1.27-rootless
|
Image=docker.io/gitea/gitea:1.27-rootless@sha256:36cce26be71609091e1236d5b5de2c66a81fb8a7d45756a5fd3b7a28c11733b7
|
||||||
# podman images --digests docker.io/gitea/gitea → paste sha256 here
|
|
||||||
Image=docker.io/gitea/gitea:1.27-rootless
|
|
||||||
# The rootless image runs as uid 1000 inside; keep-id maps it to the host user
|
# The rootless image runs as uid 1000 inside; keep-id maps it to the host user
|
||||||
# so the bind-mounted dirs stay owned by `strapi` (no chown, no root anywhere).
|
# so the bind-mounted dirs stay owned by `strapi` (no chown, no root anywhere).
|
||||||
UserNS=keep-id:uid=1000,gid=1000
|
UserNS=keep-id:uid=1000,gid=1000
|
||||||
# Shared bridge with the build containers (name-resolvable as ctao-demo-gitea);
|
# Networking: default rootless pasta (userspace — the machine's kernel lacks
|
||||||
# host loopback stays out of reach for build code — see ctao-demo.network.
|
# ip_tables for rootless netavark bridges, and we have no sudo). The build
|
||||||
Network=ctao-demo.network
|
# container joins THIS container's netns (--network=container:ctao-demo-gitea),
|
||||||
|
# so it sees Gitea on localhost:3000 while the HOST loopback stays invisible.
|
||||||
Volume=%h/ctao-portal-demo/gitea-data:/var/lib/gitea:Z
|
Volume=%h/ctao-portal-demo/gitea-data:/var/lib/gitea:Z
|
||||||
Volume=%h/ctao-portal-demo/gitea-config:/etc/gitea:Z
|
Volume=%h/ctao-portal-demo/gitea-config:/etc/gitea:Z
|
||||||
PublishPort=3000:3000
|
PublishPort=3000:3000
|
||||||
@@ -37,6 +36,10 @@ Environment=GITEA__database__DB_TYPE=sqlite3
|
|||||||
Environment=GITEA__security__INSTALL_LOCK=true
|
Environment=GITEA__security__INSTALL_LOCK=true
|
||||||
Environment=GITEA__service__DISABLE_REGISTRATION=true
|
Environment=GITEA__service__DISABLE_REGISTRATION=true
|
||||||
Environment=GITEA__mailer__ENABLED=false
|
Environment=GITEA__mailer__ENABLED=false
|
||||||
|
# First `git push` auto-creates the repo (no UI step); public so the build
|
||||||
|
# pipeline can clone anonymously — content is the public site anyway.
|
||||||
|
Environment=GITEA__repository__ENABLE_PUSH_CREATE_USER=true
|
||||||
|
Environment=GITEA__repository__DEFAULT_PRIVATE=public
|
||||||
# Sveltia is served from the portal vhost and calls the Gitea API cross-origin.
|
# Sveltia is served from the portal vhost and calls the Gitea API cross-origin.
|
||||||
# TODO(vhost): tighten * to the portal origin once known, and add
|
# TODO(vhost): tighten * to the portal origin once known, and add
|
||||||
# GITEA__cors__SCHEME=https (the [cors] SCHEME default is http).
|
# GITEA__cors__SCHEME=https (the [cors] SCHEME default is http).
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ After=network-online.target
|
|||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
ContainerName=ctao-demo-web
|
ContainerName=ctao-demo-web
|
||||||
# Official image; re-pin to the exact digest at install (same drill as Gitea).
|
# Pinned by digest at install (2026-07-28); tag kept for readability.
|
||||||
Image=docker.io/library/nginx:stable-alpine
|
Image=docker.io/library/nginx:stable-alpine@sha256:97d490c12ba55b4946b01546d1c3ed324e8d41ab1c9fcb2a616aa470620e5b46
|
||||||
# releases/ is shared with the build container (:z shared label);
|
# releases/ is shared with the build container (:z shared label);
|
||||||
# the config file is exclusive to nginx (:Z).
|
# the config file is exclusive to nginx (:Z).
|
||||||
Volume=%h/ctao-portal-demo/releases:/srv/releases:ro,z
|
Volume=%h/ctao-portal-demo/releases:/srv/releases:ro,z
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
# CTAO portal demo — dedicated bridge network (quadlet .network unit).
|
|
||||||
# Why it exists: the build container executes npm lifecycle scripts from the
|
|
||||||
# repo, so it must NOT see the host's loopback (code-server, Strapi). On this
|
|
||||||
# bridge, containers resolve each other by name (aardvark-dns) and have NATed
|
|
||||||
# internet egress, but host 127.0.0.1 services are unreachable — unlike
|
|
||||||
# --network=host, which this replaces.
|
|
||||||
[Unit]
|
|
||||||
Description=CTAO portal demo — internal bridge network
|
|
||||||
|
|
||||||
[Network]
|
|
||||||
NetworkName=ctao-demo
|
|
||||||
Reference in New Issue
Block a user