Files
homelab/userbot/k8s/base/panel.yaml
T
forustandClaude Opus 4.8 0691536f28 fix(deploy): roll out our images by digest instead of a moving tag
`kubectl rollout undo` restores the previous ReplicaSet's pod template
verbatim. While that template names a tag, the rollback does not roll back
the image: the tag has already moved, so the reverted pod pulls the very
build that just failed and the cluster stays broken. The safety net added
in 1505b63 therefore could not recover from a bad image.

Pin the digest at apply time. A digest is not knowable when a manifest is
written, so render_pinned resolves it on the way into the cluster and the
digest is never committed. Git keeps a readable `:prod`, Renovate keeps
seeing exactly the manifests it saw before, and the previous revision of
each workload now holds the digest that was actually serving, so undo
restores those exact bytes.

imagePullPolicy is dropped from the manifests rather than set to
IfNotPresent: a reference that is not `:latest` already defaults to it, and
that is what the Kubernetes docs ask for alongside a digest.

An unresolvable image is fatal instead of a warning, because carrying on
would quietly apply a mutable tag again.

restart_stale_images keeps its comparison but is no longer how a rebuild
reaches the cluster -- the pinned template rolls out on its own now. What
is left is a drift check for hand-run `kubectl set image`, so it matches
the container by repository: a pod's status now reports `repo@sha256:...`
while the manifest still says `:prod`.

The build job stops pushing `:latest` altogether, which removes the tag
that a dev branch could otherwise move under a prod deploy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-09-27 10:09:06 +02:00

266 lines
4.7 KiB
YAML

apiVersion: v1
kind: Namespace
metadata:
name: userbot
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: userbot-panel
namespace: userbot
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: userbot-runtime
namespace: userbot
automountServiceAccountToken: false
---
apiVersion: v1
kind: ConfigMap
metadata:
name: userbot-common-config
namespace: userbot
data:
DATABASE_TYPE: ""
DATABASE_NAME: ""
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: userbot-panel
namespace: userbot
rules:
- apiGroups:
- apps
resources:
- deployments
verbs:
- get
- list
- watch
- create
- patch
- delete
- apiGroups:
- apps
resources:
- deployments/scale
verbs:
- get
- patch
- update
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- pods/log
verbs:
- get
- apiGroups:
- ""
resources:
- persistentvolumeclaims
verbs:
- get
- list
- watch
- create
- delete
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
- create
- delete
- apiGroups:
- ""
resources:
- configmaps
verbs:
- get
- apiGroups:
- metrics.k8s.io
resources:
- pods
verbs:
- get
- list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: userbot-panel
namespace: userbot
subjects:
- kind: ServiceAccount
name: userbot-panel
namespace: userbot
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: userbot-panel
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: userbot-panel-legacy
namespace: default
rules:
- apiGroups:
- apps
resources:
- deployments
verbs:
- get
- list
- watch
- patch
- apiGroups:
- apps
resources:
- deployments/scale
verbs:
- get
- patch
- update
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- pods/log
verbs:
- get
- apiGroups:
- ""
resources:
- persistentvolumeclaims
verbs:
- get
- list
- apiGroups:
- metrics.k8s.io
resources:
- pods
verbs:
- get
- list
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: userbot-panel-legacy
namespace: default
subjects:
- kind: ServiceAccount
name: userbot-panel
namespace: userbot
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: userbot-panel-legacy
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: userbot-panel
namespace: userbot
labels:
app: userbot-panel
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: userbot-panel
template:
metadata:
labels:
app: userbot-panel
spec:
serviceAccountName: userbot-panel
containers:
- name: userbot-panel
image: gcr.forust.xyz/forust/userbot-panel:prod
ports:
- name: http
containerPort: 8080
env:
- name: USERBOT_NAMESPACE
value: userbot
- name: USERBOT_LEGACY_NAMESPACES
value: default
- name: USERBOT_IMAGE
value: gcr.forust.xyz/forust/userbot:latest
- name: USERBOT_STORAGE_CLASS
value: local-path-retain
- name: USERBOT_DOWNLOADS_HOST_PATH
value: /srv/homelab/userbot/Downloads
resources:
requests:
cpu: 20m
memory: 96Mi
limits:
cpu: 300m
memory: 384Mi
readinessProbe:
httpGet:
path: /api/health
port: http
initialDelaySeconds: 3
periodSeconds: 10
livenessProbe:
httpGet:
path: /api/health
port: http
initialDelaySeconds: 10
periodSeconds: 30
---
apiVersion: v1
kind: Service
metadata:
name: userbot-panel
namespace: userbot
spec:
type: ClusterIP
selector:
app: userbot-panel
ports:
- name: http
port: 8080
targetPort: http
---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: userbot-panel-route
namespace: userbot
spec:
entryPoints:
- websecure
routes:
- match: Host(`userbot.workstation.internal`)
kind: Rule
services:
- name: userbot-panel
port: 8080
tls:
secretName: internal-wildcard-tls