design / Dockerfile
Nanny7's picture
fix: reduce Node heap to 256MB, add 5min inactivity timeout for faster cleanup
cd757be
Raw
History Blame Contribute Delete
6.55 kB
# Open Design on Hugging Face Spaces
#
# To fork this with your own changes, replace the repo URL and ref below
# with your own fork.
ARG NODE_IMAGE=docker.io/library/node:24-slim
FROM ${NODE_IMAGE} AS build
RUN apt-get update && apt-get install -y git python3 make g++ && rm -rf /var/lib/apt/lists/*
ARG CACHEBUST=7
WORKDIR /src
RUN git clone --depth 1 --branch main \
https://github.com/LevelSUB-zero/Opencode.git . && \
echo "cloned at cachebust $CACHEBUST"
RUN corepack enable && \
corepack prepare pnpm@10.33.2 --activate && \
pnpm install --frozen-lockfile
RUN pnpm --filter @open-design/daemon build && \
pnpm --filter @open-design/web build && \
pnpm --filter @open-design/daemon deploy --legacy --prod /src/deploy/daemon && \
pnpm store prune && \
rm -rf /root/.cache /root/.local/share/pnpm/store
FROM ${NODE_IMAGE}
RUN apt-get update && apt-get install -y tini poppler-utils && rm -rf /var/lib/apt/lists/* && \
addgroup --system --gid 1001 open-design && \
adduser --system --no-create-home --shell /bin/sh --uid 1001 --ingroup open-design open-design
# Install opencode-ai CLI as the primary agent runtime.
RUN npm install -g opencode-ai@1.14.39 && \
opencode --version && \
chmod -R a+rX /usr/local/lib/node_modules/opencode-ai
WORKDIR /app
COPY --from=build --chown=open-design:open-design /src/deploy/daemon ./apps/daemon
COPY --from=build --chown=open-design:open-design /src/apps/web/out ./apps/web/out
COPY --from=build --chown=open-design:open-design /src/skills ./skills
COPY --from=build --chown=open-design:open-design /src/design-systems ./design-systems
COPY --from=build --chown=open-design:open-design /src/craft ./craft
COPY --from=build --chown=open-design:open-design /src/prompt-templates ./prompt-templates
COPY --from=build --chown=open-design:open-design /src/assets/frames ./assets/frames
COPY --from=build --chown=open-design:open-design /src/assets/community-pets ./assets/community-pets
COPY --from=build --chown=open-design:open-design /src/plugins/_official ./plugins/_official
RUN mkdir -p /data/.od && chown -R open-design:open-design /data
# Entrypoint: generate a runtime API token, inject it into the frontend so
# API calls carry the Authorization header, fix /data volume permissions
# (HF Spaces mounts the persistent volume at /data with root ownership),
# then drop to the unprivileged open-design user and run the daemon.
RUN cat > /entrypoint.sh << 'SCRIPT'
#!/bin/sh
set -e
export OD_API_TOKEN=$(node -e "process.stdout.write(require('crypto').randomBytes(32).toString('hex'))")
echo "OpenCode: $(opencode --version 2>&1)"
# Prep tmpfs dirs for daemon DB and opencode data (avoid SQLite I/O on HF volume)
rm -rf /tmp/od-data /tmp/opencode
mkdir -p /tmp/opencode /tmp/od-data /data/.od
# Write opencode-ai config and auth. The {env:VAR} template for apiKey
# has a known bug (#19946), so we use direct shell expansion.
if [ -n "$OPENCODE_API_KEY" ]; then
mkdir -p /data/.config/opencode
cat > /data/.config/opencode/opencode.json << OPCONFIG
{
"provider": {
"opencodezen": {
"options": {
"apiKey": "$OPENCODE_API_KEY"
}
}
}
}
OPCONFIG
# auth.json lives under XDG_DATA_HOME/opencode/ (XDG_DATA_HOME=/tmp)
cat > /tmp/opencode/auth.json << OPAUTH
{
"opencodezen": "$OPENCODE_API_KEY"
}
OPAUTH
echo "OpenCode config + auth written (opencodezen)"
fi
# Determine the public origin URL for the isLocalSameOrigin bypass
SPACE_SUBDOMAIN="${SPACE_SUBDOMAIN:-kratoszeus-design}"
export OD_ALLOWED_ORIGINS="https://${SPACE_SUBDOMAIN}.hf.space"
echo "Allowed origins: ${OD_ALLOWED_ORIGINS}"
# Inject the API token into the frontend's index.html so all fetch calls
# to /api/* automatically include Authorization: Bearer <token>.
node -e "
var fs = require('fs');
var token = process.env.OD_API_TOKEN;
var html = fs.readFileSync('apps/web/out/index.html', 'utf8');
var script = '<script>window.__OD_API_TOKEN=' + JSON.stringify(token) + ';(function(){var f=window.fetch;window.fetch=function(u,o){o=o||{};var s=typeof u===\"string\"?u:u.toString();if(s.startsWith(\"/api/\")){o.headers=o.headers||{};o.headers[\"Authorization\"]=\"Bearer \"+window.__OD_API_TOKEN}return f.call(window,u,o)}})()</script>';
if (html.indexOf('</head>') !== -1) {
html = html.replace('</head>', script + '</head>');
} else {
html = script + html;
}
fs.writeFileSync('apps/web/out/index.html', html);
"
# Patch the compiled server.js at startup:
# 1) openProbePaths uses req.originalUrl (req.path is relative to /api mount)
# 2) Patch origin-validation.js so isLocalSameOrigin trusts same-origin requests
node -e "
var fs = require('fs');
var src = fs.readFileSync('apps/daemon/dist/server.js', 'utf8');
// 1) Fix openProbePaths: TSC splits 'if(...)return next()' across two lines
src = src.replace(
'if (openProbePaths.has(req.path))\n return next();',
'if (openProbePaths.has(req.originalUrl))\n return next();'
);
fs.writeFileSync('apps/daemon/dist/server.js', src);
// 2) Patch origin-validation.js so all isLocalSameOrigin call sites trust same-origin
var ovSrc = fs.readFileSync('apps/daemon/dist/origin-validation.js', 'utf8');
ovSrc = ovSrc.replace(
'export function isLocalSameOrigin(req, port, env = process.env) {',
'export function isLocalSameOrigin(req, port, env = process.env) { var _o=headerValue(req.headers?.origin);if(typeof _o===\"string\"&&_o.length>0){try{var u=new URL(_o);if(u.host===req.headers.host)return true}catch{}}var _r=headerValue(req.headers?.referer);if(typeof _r===\"string\"&&_r.length>0){try{var u=new URL(_r);if(u.host===req.headers.host)return true}catch{}}'
);
fs.writeFileSync('apps/daemon/dist/origin-validation.js', ovSrc);
console.log('Patched: openProbePaths + origin-validation');
"
# Pass OPENCODE_API_KEY to the daemon via su -c (su drops most env vars).
chown -R open-design:open-design /tmp/opencode /tmp/od-data /data
exec su -s /bin/sh open-design -c "HOME=/data OPENCODE_API_KEY='$OPENCODE_API_KEY' XDG_DATA_HOME=/tmp OD_DATA_DIR=/tmp/od-data node apps/daemon/dist/cli.js --no-open"
SCRIPT
RUN chmod +x /entrypoint.sh
ENV NODE_ENV=production
ENV NODE_OPTIONS=--max-old-space-size=256
ENV OD_BIND_HOST=0.0.0.0
ENV OD_PORT=7860
ENV OD_DATA_DIR=/tmp/od-data
# Shorter inactivity timeout so stuck opencode processes are cleaned up faster
ENV OD_CHAT_RUN_INACTIVITY_TIMEOUT_MS=300000
# OPENCODE_API_KEY — set as a HF Space Secret for opencode-ai auth
EXPOSE 7860
ENTRYPOINT ["/usr/bin/tini", "--", "/entrypoint.sh"]