Skip to content

Instantly share code, notes, and snippets.

@worldofgeese
Last active April 19, 2026 15:01
Show Gist options
  • Select an option

  • Save worldofgeese/117ec4f349453cd3f4ff75881b0f9feb to your computer and use it in GitHub Desktop.

Select an option

Save worldofgeese/117ec4f349453cd3f4ff75881b0f9feb to your computer and use it in GitHub Desktop.
OpenClaw on rootless Podman
openclaw-gateway:
build:
context: .
dockerfile: ./Containerfile.nix
target: runtime
image: openclaw-docker-openclaw-gateway
container_name: openclaw-gateway
restart: unless-stopped
stdin_open: true
tty: true
shm_size: 10gb
mem_limit: 24g
memswap_limit: 28g
cpus: 6
security_opt:
- seccomp=unconfined
depends_on:
- docktail
- podman-in-podman
environment:
- DOCKER_HOST=tcp://podman-in-podman:2375
- NODE_ENV=production
- UMASK=002
- OPENCLAW_SKIP_SERVICE_CHECK=true
- OPENCLAW_SKIP_GMAIL_WATCHER=1
- TZ=Europe/Copenhagen
volumes:
- openclaw_data:/home/node/.openclaw:U,Z
- openclaw_config:/home/node/.config:U,Z
- openclaw_local:/home/node/.local:U,Z
- openclaw_ssh:/home/node/.ssh:U,Z
- wiki_vault:/home/node/wiki-vault:Z
- /usr/share/zoneinfo:/usr/share/zoneinfo:ro
ports:
- 2222:2222 # container sshd — tailnet-only via host
labels:
- "docktail.service.enable=true"
- "docktail.service.name=openclaw"
- "docktail.service.port=18790"
- "docktail.service.service-protocol=https"
docktail:
image: ghcr.io/marvinvr/docktail:latest
container_name: docktail
restart: unless-stopped
depends_on:
- tailscale
volumes:
- tailscale-socket:/var/run/tailscale
environment:
- DOCKER_HOST=tcp://host.containers.internal:2377
- TAILSCALE_OAUTH_CLIENT_ID=${TAILSCALE_OAUTH_CLIENT_ID}
- TAILSCALE_OAUTH_CLIENT_SECRET=${TAILSCALE_OAUTH_CLIENT_SECRET}
podman-in-podman:
image: quay.io/podman/stable
container_name: podman-in-podman
privileged: true
environment:
- _CONTAINERS_USERNS_CONFIGURED=""
volumes:
- podman_data:/var/lib/containers:Z
security_opt:
- label=disable
command: ["podman", "system", "service", "-t", "0", "tcp:0.0.0.0:2375"]
restart: unless-stopped
# =============================================================================
# Stage 1: Builder - Compile OpenClaw
# =============================================================================
FROM docker.io/library/node:24-bookworm AS builder
RUN apt-get update && apt-get install -y --no-install-recommends \
git curl ca-certificates python3 build-essential \
&& rm -rf /var/lib/apt/lists/*
RUN corepack enable
WORKDIR /app
ARG OPENCLAW_VERSION=v2026.4.15
ARG OPENCLAW_EXTENSIONS=""
RUN git clone --depth 1 --branch ${OPENCLAW_VERSION} \
https://github.com/openclaw/openclaw.git .
ENV CI=true
# Cache pnpm store across builds; OOM guard for low-memory hosts
RUN --mount=type=cache,id=openclaw-pnpm-store,target=/root/.local/share/pnpm/store,sharing=locked \
NODE_OPTIONS=--max-old-space-size=2048 pnpm install --frozen-lockfile
# A2UI: stub gracefully if cross-compiling
RUN pnpm canvas:a2ui:bundle || \
(echo "A2UI bundle: creating stub (non-fatal)" && \
mkdir -p src/canvas-host/a2ui && \
echo "/* A2UI bundle unavailable */" > src/canvas-host/a2ui/a2ui.bundle.js && \
echo "stub" > src/canvas-host/a2ui/.bundle.hash)
# Use the upstream Docker build sequence rather than a partial manual copy.
# This preserves the required post-build ordering for bundled chat metadata.
RUN NODE_OPTIONS=--max-old-space-size=2048 pnpm build:docker
# Force pnpm for UI build (Bun may fail on ARM/Synology architectures)
ENV OPENCLAW_PREFER_PNPM=1
RUN pnpm ui:build
# =============================================================================
# Stage 2: Prune - Strip dev deps and build artifacts
# =============================================================================
FROM builder AS runtime-assets
RUN CI=true pnpm prune --prod && \
find dist -type f \( -name '*.d.ts' -o -name '*.d.mts' -o -name '*.d.cts' -o -name '*.map' \) -delete && \
rm -rf .git node_modules/.cache src
# =============================================================================
# Stage 3: Runtime - Chainguard + Nix + Devbox
# =============================================================================
FROM cgr.dev/chainguard/node:latest-dev AS runtime
USER root
RUN apk add --no-cache curl bash xz git coreutils openssh tmux libcap
ARG USER_UID=1001
ARG HOME_DIR=/home/node
# Recreate node user with deterministic UID
RUN deluser node 2>/dev/null || true && \
adduser -D -u ${USER_UID} -G root -h ${HOME_DIR} -s /bin/bash node
# Core runtime paths
RUN mkdir -p /nix /app ${HOME_DIR} && \
chown -R ${USER_UID}:0 /nix /app ${HOME_DIR} && \
chmod -R g=u /nix /app ${HOME_DIR}
ENV HOME=${HOME_DIR}
ENV NIX_PROFILES="/nix/var/nix/profiles/default ${HOME_DIR}/.nix-profile"
ENV PATH="${HOME_DIR}/.nix-profile/bin:/nix/var/nix/profiles/default/bin:${PATH}"
USER node
WORKDIR ${HOME_DIR}
# Install Nix + Devbox
RUN curl -L https://nixos.org/nix/install | bash -s -- --no-daemon && \
. "${HOME_DIR}/.nix-profile/etc/profile.d/nix.sh" && \
nix-env -iA nixpkgs.devbox && \
command -v nix-env && \
command -v devbox
USER root
WORKDIR /app
# Copy pruned application
COPY --from=runtime-assets --chown=1001:0 /app/dist /app/dist
COPY --from=runtime-assets --chown=1001:0 /app/node_modules /app/node_modules
COPY --from=runtime-assets --chown=1001:0 /app/package.json /app/package.json
COPY --from=runtime-assets --chown=1001:0 /app/openclaw.mjs /app/openclaw.mjs
COPY --from=runtime-assets --chown=1001:0 /app/extensions /app/extensions
COPY --from=runtime-assets --chown=1001:0 /app/skills /app/skills
COPY --from=runtime-assets --chown=1001:0 /app/docs /app/docs
RUN ln -sf /app/openclaw.mjs /usr/local/bin/openclaw && \
chmod 755 /app/openclaw.mjs
USER node
WORKDIR /app
ENV NODE_ENV=production
ENV OPENCLAW_BUNDLED_PLUGINS_DIR=/app/extensions
# =============================================================================
# Entrypoint
# =============================================================================
COPY --chmod=0755 <<'ENTRY' /usr/local/bin/openclaw-entrypoint
#!/bin/bash
set -e
# Rebuild nix profile links on every boot.
# ~/.local is a persistent volume that can preserve stale profile symlinks
# from previous image builds. Rebuild from the nix store each time.
PROFILES_DIR="$HOME/.local/state/nix/profiles"
rm -rf "$PROFILES_DIR" 2>/dev/null || true
mkdir -p "$PROFILES_DIR"
for env in /nix/store/*-user-environment; do
if [ -x "$env/bin/nix" ]; then
ln -sf "$env" "$PROFILES_DIR/profile-1-link"
ln -sf "profile-1-link" "$PROFILES_DIR/profile"
break
fi
done
# Source nix shell hooks
if [ -f "$HOME/.nix-profile/etc/profile.d/nix.sh" ]; then
. "$HOME/.nix-profile/etc/profile.d/nix.sh"
elif [ -f "/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh" ]; then
. "/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh"
fi
# Ensure devbox is on PATH
if ! command -v devbox >/dev/null 2>&1; then
DEVBOX_STORE="$(find /nix/store -maxdepth 1 -type d -name '*-devbox-*' 2>/dev/null | head -n 1)"
if [ -n "$DEVBOX_STORE" ] && [ -x "$DEVBOX_STORE/bin/devbox" ]; then
export PATH="$DEVBOX_STORE/bin:$PATH"
fi
fi
# Optional persistent Devbox environment
DEVBOX_DIR="$HOME/.openclaw/devbox-env"
if [ -f "$DEVBOX_DIR/devbox.json" ]; then
if command -v devbox >/dev/null 2>&1; then
eval "$(cd "$DEVBOX_DIR" && devbox shellenv 2>/dev/null)"
else
echo "Warning: devbox not found in PATH" >&2
fi
fi
# Start sshd if configured
SSHD_CONFIG="$HOME/.config/sshd/sshd_config"
if [ -f "$SSHD_CONFIG" ]; then
SSHD_BIN="$(command -v sshd 2>/dev/null)"
if [ -n "$SSHD_BIN" ]; then
"$SSHD_BIN" -f "$SSHD_CONFIG" -E "$HOME/.config/sshd/sshd.log"
fi
fi
exec "$@"
ENTRY
ENTRYPOINT ["/usr/local/bin/openclaw-entrypoint"]
CMD ["node", "openclaw.mjs", "gateway", "--allow-unconfigured"]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment