Adding local tags to images and enforcing them at pods
Homelab Main / deploy (push) Successful in 1m21s Details

This commit is contained in:
juvdiaz 2026-05-29 12:23:44 -06:00
parent 6e78988a6a
commit 1f6799271a
8 changed files with 104 additions and 24 deletions

View File

@ -528,9 +528,17 @@ them through the edge path at `/demo-apps/`.
`./lab.sh up` builds and pushes two independent images:
- `php-website:latest` from `apps/website`
- a content-hash `php-website` tag generated by `lab.sh` and passed to Argo CD
as a Kustomize image override
- `demos-static:latest` from `apps/demos-static`
The website manifest keeps the stable base image name `php-website:bootstrap`.
During bootstrap, `lab.sh` hashes `apps/website`, builds
`<registry>/php-website:src-<hash>`, exports that exact reference through
`TF_VAR_website_image_ref`, and the Argo CD Application applies it through
Kustomize. This keeps the GitOps source generic while the deployed image remains
immutable.
The first demo, `The Client-Side Media Cruncher (Wasm + TS)`, currently performs
private, browser-only image compression and conversion using native Canvas APIs.
Heavier video conversion, such as MP4 to WebM, should use a Rust core compiled
@ -566,9 +574,9 @@ Current demo inventory:
- Model drift simulator: visual MLOps playground for spikes, corrupted inputs,
and retraining.
The Kubernetes deployment uses `apps/website/web-app.yaml`. Keep the image
reference there aligned with `TF_VAR_registry_endpoint`, because `lab.sh` derives
the registry endpoint from that manifest.
The Kubernetes deployment uses `apps/website/web-app.yaml` as a Kustomize base.
Keep `TF_VAR_registry_endpoint` aligned with the local registry endpoint used by
the app image build.
Keep the `.terraform.lock.hcl` files committed. They pin provider selections and
make bootstrap behavior reproducible across nodes and rebuilds.

View File

@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- web-app.yaml

View File

@ -175,7 +175,7 @@ return [
'blog_todo_1' => 'Move Gitea data from the Raspberry Pi SD card to SSD-backed storage.',
'blog_todo_2' => 'Keep the Debian bare GitOps mirror as the cluster source and add object-storage backups when OCI storage is ready.',
'blog_todo_3' => 'Add a real OpenTofu remote state backend with backup, locking, and a documented recovery path.',
'blog_todo_4' => 'Replace mutable latest image references with immutable tags or digest pins for website and demo workloads.',
'blog_todo_4' => 'Replace the remaining mutable latest image references with immutable tags or digest pins for demo workloads; the website image now uses a content-hash tag.',
'blog_todo_5' => 'Generate SBOMs and sign images so the local registry can prove what it is serving.',
'blog_todo_6' => 'Add Renovate or Dependabot-style dependency updates for base images, Helm charts, and GitHub/Gitea Actions.',
'blog_todo_7' => 'Expand Kyverno baseline policy coverage: non-root, read-only roots, resource requests, allowed registries, and documented exceptions for platform components.',

View File

@ -175,7 +175,7 @@ return [
'blog_todo_1' => 'Move Gitea data from Raspberry Pi SD card to SSD-backed storage.',
'blog_todo_2' => 'Keep Debian bare GitOps mirror as cluster source ihuan add object-storage backups quema OCI storage ready.',
'blog_todo_3' => 'Add real OpenTofu remote state backend ika backup, locking, ihuan documented recovery path.',
'blog_todo_4' => 'Replace mutable latest image references ika immutable tags o digest pins para website ihuan demo workloads.',
'blog_todo_4' => 'Replace remaining mutable latest image references ika immutable tags o digest pins para demo workloads; website image axcan uses content-hash tag.',
'blog_todo_5' => 'Generate SBOMs ihuan sign images so local registry can prove tlein serving.',
'blog_todo_6' => 'Add Renovate o Dependabot-style dependency updates para base images, Helm charts, ihuan GitHub/Gitea Actions.',
'blog_todo_7' => 'Expand Kyverno baseline policy coverage: non-root, read-only roots, resource requests, allowed registries, ihuan documented exceptions para platform components.',

View File

@ -55,8 +55,8 @@ spec:
topologyKey: "kubernetes.io/hostname"
containers:
- name: php-app
image: 192.168.100.68:30500/php-website:latest
imagePullPolicy: Always
image: php-website:bootstrap
imagePullPolicy: IfNotPresent
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true

View File

@ -12,6 +12,23 @@ provider "kubernetes" {
config_path = var.kubeconfig_path
}
locals {
application_sources = {
for name, application in var.applications : name => merge(
{
repoURL = var.gitops_repo_url
targetRevision = application.target_revision
path = application.path
},
name == "website-production" && var.website_image_ref != "" ? {
kustomize = {
images = ["php-website=${var.website_image_ref}"]
}
} : {}
)
}
}
resource "kubernetes_manifest" "argocd_application" {
for_each = var.applications
@ -28,11 +45,7 @@ resource "kubernetes_manifest" "argocd_application" {
}
spec = {
project = each.value.project
source = {
repoURL = var.gitops_repo_url
targetRevision = each.value.target_revision
path = each.value.path
}
source = local.application_sources[each.key]
destination = {
server = "https://kubernetes.default.svc"
namespace = each.value.namespace

View File

@ -13,6 +13,11 @@ variable "gitops_repo_url" {
default = "ssh://jv@192.168.100.68/home/jv/git-server/my-homelab-configs.git"
}
variable "website_image_ref" {
type = string
default = ""
}
variable "applications" {
type = map(object({
project = string

71
lab.sh
View File

@ -1363,16 +1363,32 @@ cleanup_node() {
sudo systemctl start containerd 2>/dev/null || true
}
website_registry_endpoint() {
local image
image_ref_tag() {
local image_ref="$1"
local tag
image="$(awk '$1 == "image:" && $2 ~ /php-website/ {print $2; exit}' "${REPO_ROOT}/apps/website/web-app.yaml")"
if [[ -z "${image}" || "${image}" != */* ]]; then
echo "Could not determine website registry endpoint from apps/website/web-app.yaml" >&2
tag="${image_ref##*:}"
if [[ "${tag}" == "${image_ref}" || "${tag}" == */* ]]; then
echo "Image reference ${image_ref} must include an immutable tag." >&2
exit 1
fi
printf '%s\n' "${image%%/*}"
printf '%s\n' "${tag}"
}
website_image_tag() {
local source_hash="$1"
printf 'src-%s\n' "${source_hash:0:12}"
}
apps_registry_endpoint() {
if [[ -n "${TF_VAR_registry_endpoint:-}" ]]; then
printf '%s\n' "${TF_VAR_registry_endpoint}"
return 0
fi
demos_registry_endpoint
}
demos_registry_endpoint() {
@ -1430,6 +1446,7 @@ website_image_is_current() {
local platforms="$3"
local image_ref="$4"
local registry_endpoint="$5"
local image_tag
local saved_hash
local saved_platforms
local saved_image
@ -1444,7 +1461,27 @@ website_image_is_current() {
[[ "${saved_platforms}" == "${platforms}" ]] || return 1
[[ "${saved_image}" == "${image_ref}" ]] || return 1
registry_image_exists "${registry_endpoint}" php-website latest
image_tag="$(image_ref_tag "${image_ref}")"
registry_image_exists "${registry_endpoint}" php-website "${image_tag}"
}
ensure_website_image_tag_not_reused() {
local state_file="$1"
local source_hash="$2"
local image_ref="$3"
local saved_hash
local saved_image
[[ -f "${state_file}" ]] || return 0
saved_hash="$(image_state_value "${state_file}" source_hash)"
saved_image="$(image_state_value "${state_file}" image)"
if [[ -n "${saved_hash}" && -n "${saved_image}" && "${saved_hash}" != "${source_hash}" && "${saved_image}" == "${image_ref}" ]]; then
echo "Website source changed but the computed php-website image ref is still ${image_ref}." >&2
echo "Check WEBSITE_IMAGE_TAG or the website image tag generator before rebuilding." >&2
exit 1
fi
}
demos_image_is_current() {
@ -2533,30 +2570,42 @@ apps() {
require_debian_server "apps"
registry_endpoint="$(website_registry_endpoint)"
registry_endpoint="$(apps_registry_endpoint)"
demos_registry_endpoint="$(demos_registry_endpoint)"
demos_image_ref="${registry_endpoint}/demos-static:latest"
demos_image_state_file="${REPO_ROOT}/.lab/demos-static-image.state"
demos_platforms="${DEMOS_IMAGE_PLATFORMS:-linux/arm64}"
demos_source_hash="$(demos_source_hash)"
website_image_ref="${registry_endpoint}/php-website:latest"
website_image_state_file="${REPO_ROOT}/.lab/php-website-image.state"
website_platforms="${WEBSITE_IMAGE_PLATFORMS:-linux/arm64}"
website_source_hash="$(website_source_hash)"
website_image_ref="${registry_endpoint}/php-website:${WEBSITE_IMAGE_TAG:-$(website_image_tag "${website_source_hash}")}"
export TF_VAR_registry_endpoint="${TF_VAR_registry_endpoint:-${registry_endpoint}}"
export TF_VAR_website_image_ref="${TF_VAR_website_image_ref:-${website_image_ref}}"
export TF_VAR_kubeconfig_path="${TF_VAR_kubeconfig_path:-${KUBECONFIG_PATH}}"
export KUBECONFIG="${TF_VAR_kubeconfig_path}"
if [[ "${TF_VAR_registry_endpoint}" != "${registry_endpoint}" ]]; then
echo "TF_VAR_registry_endpoint must match apps/website/web-app.yaml (${registry_endpoint})" >&2
echo "TF_VAR_registry_endpoint changed after registry endpoint resolution (${registry_endpoint})" >&2
exit 1
fi
if [[ "${TF_VAR_website_image_ref}" != "${website_image_ref}" ]]; then
echo "TF_VAR_website_image_ref must match the buildx website image ref (${website_image_ref})" >&2
exit 1
fi
if [[ "${demos_registry_endpoint}" != "${registry_endpoint}" ]]; then
echo "apps/demos-static/web-app.yaml registry endpoint (${demos_registry_endpoint}) must match apps/website/web-app.yaml (${registry_endpoint})" >&2
echo "apps/demos-static/web-app.yaml registry endpoint (${demos_registry_endpoint}) must match app registry endpoint (${registry_endpoint})" >&2
exit 1
fi
if [[ "$(image_ref_tag "${website_image_ref}")" == "latest" ]]; then
echo "apps/website/web-app.yaml must use an immutable php-website image tag, not latest." >&2
exit 1
fi
ensure_website_image_tag_not_reused "${website_image_state_file}" "${website_source_hash}" "${website_image_ref}"
echo "Deploying homelab applications..."
run_tofu_stack "bootstrap/apps"