OpenClaw deployment for OpenShift: UBI 10 · Node.js 24 · OpenRouter · Telegram
Runs OpenClaw as a Pod on OpenShift/OKD, or as a systemd/Podman Quadlet on a Linux host with systemd and Podman. Built on Red Hat UBI 10 + Node.js 24 (not Debian, not Ubuntu), hardened by default, published to GHCR via GitHub Actions.
Agent interaction is through Telegram — no Ingress or Route needed.
occonfigured against your cluster- An OpenRouter API key
- A Telegram bot token from @BotFather
- A GitHub account to push the container image to GHCR
Push to main (or tag a release); GitHub Actions builds and publishes to ghcr.io/rarguello/shiftclaw.
Update the image reference in manifests/statefulset.yaml:
image: ghcr.io/rarguello/shiftclaw:2026.6.34Create a local .env file (gitignored). oc create secret --from-env-file reads it literally — generate the gateway token first, then paste the value in:
openssl rand -hex 32OPENROUTER_API_KEY=sk-or-...
TELEGRAM_BOT_TOKEN=123456:ABC-...
OPENCLAW_GATEWAY_TOKEN=<paste the generated value>
oc apply -f manifests/namespace.yaml
oc create secret generic shiftclaw --from-env-file=.env --namespace=shiftclawoc apply -k manifests/Creates the ServiceAccount, ConfigMap, StatefulSet, NetworkPolicy, PodDisruptionBudget, and Service. secret.yaml is excluded from kustomization.yaml on purpose — it's never committed.
oc get pods -n shiftclaw -w
oc logs -l app.kubernetes.io/name=shiftclaw -n shiftclaw -fFirst start requires approving the Telegram pairing:
oc exec -it shiftclaw-0 -n shiftclaw -- sh
openclaw pairing approve telegram <PAIRING_CODE>Pairing code is in the pod logs. Persisted on the PVC — one-time only.
Agent interaction is through Telegram. For local access to the gateway (port 18789):
oc port-forward pod/shiftclaw-0 18789:18789 -n shiftclaw| File | Purpose |
|---|---|
config/openclaw.json |
Non-sensitive runtime config — model, channels, agent defaults |
manifests/secret.yaml.template |
Documents the expected Secret structure |
manifests/serviceaccount.yaml |
Dedicated ServiceAccount (no default SA token mounted) |
manifests/networkpolicy.yaml |
Default-deny ingress; egress limited to DNS + HTTPS only |
manifests/poddisruptionbudget.yaml |
Signals voluntary-disruption intent to the scheduler |
manifests/statefulset.yaml |
StatefulSet — resource limits, probes, security context, PVC template |
package.json |
Pins the OpenClaw npm version the build reads (Dependabot tracks this) |
Edit config/openclaw.json to change the model, enable/disable channels, or tune agent parameters.
Config is seeded from the ConfigMap on first start only — OpenClaw owns it at runtime after that. To apply a ConfigMap change, delete the live config so the init container re-seeds it:
oc create configmap shiftclaw-config \
--from-file=openclaw.json=config/openclaw.json \
--namespace=shiftclaw \
--dry-run=client -o yaml | oc apply -f -
oc exec shiftclaw-0 -n shiftclaw -- rm /var/lib/openclaw/openclaw.json
oc rollout restart statefulset/shiftclaw -n shiftclaw- Update the
openclawversion inpackage.json(or merge the Dependabot PR) — this is what the build actually reads - Push — CI builds, boot-tests, scans, and publishes automatically;
latestonly moves once all three pass - Update the version string everywhere else it's referenced for documentation/deployment purposes:
manifests/statefulset.yaml(bothimage:fields),shiftclaw.container,run.sh, and the README badge/examples oc apply -k manifests/
- Non-root process (
runAsNonRoot: true) — OpenShift SCC assigns the UID - Read-only root filesystem (
readOnlyRootFilesystem: true) - All Linux capabilities dropped (
capabilities: drop: ALL) - Default seccomp profile (
seccompProfile: RuntimeDefault) - No privilege escalation (
allowPrivilegeEscalation: false) - Dedicated ServiceAccount with no token mounted (
automountServiceAccountToken: false) - NetworkPolicy: default-deny ingress; egress limited to DNS (5353/UDP to openshift-dns) + HTTPS (443)
- Secrets never baked into the image — injected at runtime via K8s Secret
- Every image build is scanned with Trivy; CRITICAL CVEs fail the pipeline
- SBOM and provenance attestations generated on every push
Recommended for running ShiftClaw persistently outside OpenShift — no root required.
Quadlet (Podman 4.4+) lets systemd manage containers directly from a .container file.
Same as Testing locally with Podman below: OpenRouter API key, Telegram bot token, Podman installed.
mkdir -p ~/.local/share/shiftclaw
cp config/openclaw.json ~/.local/share/shiftclaw/openclaw.jsonOpenClaw manages this file at runtime after the first copy.
mkdir -p ~/.config/shiftclaw
cp .env.example ~/.config/shiftclaw/envFill in real values. Lives outside the repo.
mkdir -p ~/.config/containers/systemd
cp shiftclaw.container ~/.config/containers/systemd/Lets user services start at boot and survive logout:
loginctl enable-lingersystemctl --user daemon-reload
systemctl --user enable --now shiftclawThe Quadlet tracks :latest and pulls on every restart. For it to update automatically, enable Podman's own timer:
systemctl --user enable --now podman-auto-update.timer# Follow logs
journalctl --user -u shiftclaw -f
# Approve the Telegram pairing (first start only)
podman exec shiftclaw openclaw pairing approve telegram <PAIRING_CODE>
# Stop / restart
systemctl --user stop shiftclaw
systemctl --user restart shiftclawUse a ChatGPT Plus/Pro/Business subscription instead of API billing — OpenClaw authenticates via OAuth.
Container must be running:
podman exec -it shiftclaw openclaw models auth login --provider openaiOpen the printed URL, log in with your ChatGPT account, authorize. It redirects to a http://localhost:1455/... URL — copy that full URL back into the terminal. One-time; credentials persist in the state directory.
podman exec -it shiftclaw openclaw config set agents.defaults.model.primary openai/gpt-5.5podman exec -it shiftclaw openclaw models list --provider openai
config/openclaw.jsononly seeds state on first run — OpenClaw owns it afterward. Drift from the repo copy is expected.
- OpenRouter API key — openrouter.ai → Keys → Create key (
sk-or-...) - Telegram bot token — message @BotFather,
/newbot - Podman — podman.io/docs/installation
cp .env.example .envFill in the values (each is explained in .env.example):
OPENROUTER_API_KEY=sk-or-...
TELEGRAM_BOT_TOKEN=123456789:ABCdef...
OPENCLAW_GATEWAY_TOKEN=any-random-string
For local code changes:
podman build -t localhost/shiftclaw:dev .# Published image:
./run.sh
# Locally built image:
./run.sh localhost/shiftclaw:devThe script seeds ~/.local/share/shiftclaw/ with config/openclaw.json, mounts it persistently, loads secrets from .env, and exposes the gateway on http://localhost:18789. Look for [gateway] ready in the logs.
First start prints a pairing code:
[telegram] pairing code: 123456
Approve it (container must be running):
podman exec shiftclaw openclaw pairing approve telegram 123456Saved to state — one-time only.
Message your bot in Telegram. Ctrl+C to stop.
MIT — same as OpenClaw upstream. Red Hat, OpenShift, and UBI are trademarks of Red Hat, Inc.
