mirror of
https://github.com/Skyvern-AI/skyvern.git
synced 2026-07-09 16:09:13 +00:00
fix(SKY-9781): port prebuilt-UI Dockerfile + entrypoint from cloud (#5998)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
07da906667
commit
a0fc148a1e
3 changed files with 141 additions and 27 deletions
|
|
@ -1,28 +1,61 @@
|
|||
FROM node:20.12-slim
|
||||
|
||||
# Install tini for proper signal handling and zombie reaping
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends tini && rm -rf /var/lib/apt/lists/*
|
||||
# Stage 1: Build frontend assets
|
||||
FROM node:24.14-slim AS builder
|
||||
|
||||
WORKDIR /app
|
||||
COPY ./skyvern-frontend /app
|
||||
COPY ./entrypoint-skyvernui.sh /app/entrypoint-skyvernui.sh
|
||||
RUN npm install
|
||||
|
||||
# Placeholders for runtime injection (will be replaced by entrypoint script)
|
||||
# Copy dependency files first for better Docker layer caching
|
||||
COPY ./skyvern-frontend/package.json ./skyvern-frontend/package-lock.json ./
|
||||
RUN npm ci
|
||||
|
||||
# Copy source code
|
||||
COPY ./skyvern-frontend/ ./
|
||||
|
||||
# Placeholders for runtime injection (replaced by entrypoint script at container start).
|
||||
# Every VITE_* variable that the frontend reads via import.meta.env must have a
|
||||
# placeholder here so Vite bakes the placeholder string into the JS bundle,
|
||||
# allowing sed replacement at container start.
|
||||
ENV VITE_API_BASE_URL=__VITE_API_BASE_URL_PLACEHOLDER__
|
||||
ENV VITE_WSS_BASE_URL=__VITE_WSS_BASE_URL_PLACEHOLDER__
|
||||
ENV VITE_ARTIFACT_API_BASE_URL=__VITE_ARTIFACT_API_BASE_URL_PLACEHOLDER__
|
||||
ENV VITE_SKYVERN_API_KEY=__SKYVERN_API_KEY_PLACEHOLDER__
|
||||
ENV VITE_BROWSER_STREAMING_MODE=__VITE_BROWSER_STREAMING_MODE_PLACEHOLDER__
|
||||
ENV VITE_ENABLE_LOG_ARTIFACTS=__VITE_ENABLE_LOG_ARTIFACTS_PLACEHOLDER__
|
||||
ENV VITE_ENABLE_CODE_BLOCK=__VITE_ENABLE_CODE_BLOCK_PLACEHOLDER__
|
||||
ENV VITE_ENABLE_2FA_NOTIFICATIONS=__VITE_ENABLE_2FA_NOTIFICATIONS_PLACEHOLDER__
|
||||
|
||||
# APP_VERSION is baked into the JS bundle at build time via Vite's define.
|
||||
# Pass --build-arg APP_VERSION=$(git rev-parse HEAD) when building the image.
|
||||
ARG APP_VERSION=development
|
||||
ENV APP_VERSION=$APP_VERSION
|
||||
|
||||
# Build at image time
|
||||
# Vite's rollup phase needs more than V8's default ~2GB heap.
|
||||
ENV NODE_OPTIONS=--max-old-space-size=4096
|
||||
|
||||
# Build assets at image build time (with placeholder values baked in)
|
||||
RUN npm run build
|
||||
|
||||
# Use tini as init for proper signal handling and zombie reaping
|
||||
# Stage 2: Runtime image (only built assets + servers)
|
||||
FROM node:24.14-slim
|
||||
|
||||
# Install tini for proper signal handling and zombie reaping
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends tini && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy built assets and server files from builder
|
||||
COPY --from=builder /app/dist ./dist
|
||||
COPY --from=builder /app/localServer.js ./
|
||||
COPY --from=builder /app/artifactServer.js ./
|
||||
COPY --from=builder /app/package.json ./
|
||||
COPY --from=builder /app/package-lock.json ./
|
||||
|
||||
# Install only production dependencies needed for the servers
|
||||
RUN npm ci --omit=dev
|
||||
|
||||
COPY ./entrypoint-skyvernui.sh ./entrypoint-skyvernui.sh
|
||||
RUN chmod +x ./entrypoint-skyvernui.sh
|
||||
|
||||
EXPOSE 8080 9090
|
||||
|
||||
ENTRYPOINT ["/usr/bin/tini", "--"]
|
||||
CMD ["/bin/bash", "/app/entrypoint-skyvernui.sh"]
|
||||
CMD ["/bin/bash", "./entrypoint-skyvernui.sh"]
|
||||
|
|
|
|||
15
Dockerfile.ui.dockerignore
Normal file
15
Dockerfile.ui.dockerignore
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# Per-Dockerfile ignore for Dockerfile.ui (overrides .dockerignore).
|
||||
# The UI image only needs the frontend source and the entrypoint script;
|
||||
# everything else in the repo is irrelevant to this build context.
|
||||
|
||||
*
|
||||
|
||||
!skyvern-frontend
|
||||
!entrypoint-skyvernui.sh
|
||||
|
||||
# Re-exclude noise inside skyvern-frontend so host-side state (a populated
|
||||
# node_modules whose .bin shims don't resolve inside Linux, a stale dist/,
|
||||
# local .env files) can't leak into the build.
|
||||
skyvern-frontend/node_modules
|
||||
skyvern-frontend/dist
|
||||
skyvern-frontend/.env*
|
||||
|
|
@ -1,21 +1,45 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
set -euo pipefail
|
||||
|
||||
# Escape special sed characters in a value so it can be used safely in a
|
||||
# sed replacement string (s|old|new|g). Handles |, &, \, and / which are
|
||||
# metacharacters. Only handles single-line values (sufficient for URLs,
|
||||
# API keys, booleans, and path prefixes used here); a multi-line value
|
||||
# would corrupt the bundle silently, so fail fast instead.
|
||||
escape_sed() {
|
||||
if [[ "$1" == *$'\n'* ]]; then
|
||||
echo "ERROR: escape_sed called with multi-line value; refusing to corrupt the bundle" >&2
|
||||
exit 1
|
||||
fi
|
||||
printf '%s\n' "$1" | sed -e 's/[&/\|]/\\&/g'
|
||||
}
|
||||
|
||||
# Default values for environment variables
|
||||
VITE_API_BASE_URL="${VITE_API_BASE_URL:-http://localhost:8000/api/v1}"
|
||||
VITE_WSS_BASE_URL="${VITE_WSS_BASE_URL:-ws://localhost:8000/api/v1}"
|
||||
VITE_ARTIFACT_API_BASE_URL="${VITE_ARTIFACT_API_BASE_URL:-http://localhost:9090}"
|
||||
VITE_BROWSER_STREAMING_MODE="${VITE_BROWSER_STREAMING_MODE:-vnc}"
|
||||
VITE_ENABLE_LOG_ARTIFACTS="${VITE_ENABLE_LOG_ARTIFACTS:-false}"
|
||||
VITE_ENABLE_CODE_BLOCK="${VITE_ENABLE_CODE_BLOCK:-false}"
|
||||
VITE_ENABLE_2FA_NOTIFICATIONS="${VITE_ENABLE_2FA_NOTIFICATIONS:-false}"
|
||||
|
||||
# Priority for VITE_SKYVERN_API_KEY:
|
||||
# 1. Environment variable (from .env file or docker-compose environment),
|
||||
# but only if it looks like a real key (not a placeholder)
|
||||
# but only if it's not a placeholder/sentinel value. Both the Dockerfile
|
||||
# default (__SKYVERN_API_KEY_PLACEHOLDER__) and the .env.example default
|
||||
# (YOUR_API_KEY) can leak through if users skip configuration. Keep this
|
||||
# filter list aligned with the frontend's PLACEHOLDER_VALUES.
|
||||
# 2. Generated credentials file from the backend first-run setup
|
||||
# (volume-mounted at /app/.skyvern/credentials.toml in docker-compose)
|
||||
VITE_SKYVERN_API_KEY="${VITE_SKYVERN_API_KEY:-}"
|
||||
SKYVERN_CREDENTIALS_FILE="${SKYVERN_CREDENTIALS_FILE:-/app/.skyvern/credentials.toml}"
|
||||
GENERATED_KEY=$(sed -n 's/.*cred\s*=\s*"\([^"]*\)".*/\1/p' "$SKYVERN_CREDENTIALS_FILE" 2>/dev/null || echo "")
|
||||
if [ -n "$VITE_SKYVERN_API_KEY" ] && [ "$VITE_SKYVERN_API_KEY" != "YOUR_API_KEY" ]; then
|
||||
VITE_SKYVERN_API_KEY=$(echo "$VITE_SKYVERN_API_KEY" | xargs)
|
||||
if [ -n "$VITE_SKYVERN_API_KEY" ] \
|
||||
&& [ "$VITE_SKYVERN_API_KEY" != "__SKYVERN_API_KEY_PLACEHOLDER__" ] \
|
||||
&& [ "$VITE_SKYVERN_API_KEY" != "YOUR_API_KEY" ]; then
|
||||
VITE_SKYVERN_API_KEY="${VITE_SKYVERN_API_KEY#"${VITE_SKYVERN_API_KEY%%[![:space:]]*}"}"
|
||||
VITE_SKYVERN_API_KEY="${VITE_SKYVERN_API_KEY%"${VITE_SKYVERN_API_KEY##*[![:space:]]}"}"
|
||||
echo "Using VITE_SKYVERN_API_KEY from environment variable"
|
||||
elif [ -n "$GENERATED_KEY" ]; then
|
||||
VITE_SKYVERN_API_KEY="$GENERATED_KEY"
|
||||
|
|
@ -25,18 +49,60 @@ else
|
|||
VITE_SKYVERN_API_KEY=""
|
||||
fi
|
||||
|
||||
# Inject environment variables into pre-built JS files (replace placeholders)
|
||||
# Using | as delimiter since URLs contain /
|
||||
find /app/dist -name "*.js" -exec sed -i \
|
||||
-e "s|__VITE_API_BASE_URL_PLACEHOLDER__|${VITE_API_BASE_URL}|g" \
|
||||
-e "s|__VITE_WSS_BASE_URL_PLACEHOLDER__|${VITE_WSS_BASE_URL}|g" \
|
||||
-e "s|__VITE_ARTIFACT_API_BASE_URL_PLACEHOLDER__|${VITE_ARTIFACT_API_BASE_URL}|g" \
|
||||
-e "s|__SKYVERN_API_KEY_PLACEHOLDER__|${VITE_SKYVERN_API_KEY}|g" \
|
||||
-e "s|__VITE_BROWSER_STREAMING_MODE_PLACEHOLDER__|${VITE_BROWSER_STREAMING_MODE}|g" \
|
||||
{} \;
|
||||
echo "Injecting runtime environment variables into pre-built assets..."
|
||||
|
||||
# Start the servers (no rebuild needed)
|
||||
# Tini (configured as ENTRYPOINT) handles signal forwarding and zombie reaping
|
||||
# NOTE: Placeholder replacement is one-shot (sed -i modifies files in place).
|
||||
# If environment variables change, the container must be recreated (not just
|
||||
# restarted) to apply new values.
|
||||
|
||||
# Escape all values for safe use in sed replacement patterns
|
||||
ESC_API_BASE_URL=$(escape_sed "$VITE_API_BASE_URL")
|
||||
ESC_WSS_BASE_URL=$(escape_sed "$VITE_WSS_BASE_URL")
|
||||
ESC_ARTIFACT_API_BASE_URL=$(escape_sed "$VITE_ARTIFACT_API_BASE_URL")
|
||||
ESC_SKYVERN_API_KEY=$(escape_sed "$VITE_SKYVERN_API_KEY")
|
||||
ESC_BROWSER_STREAMING_MODE=$(escape_sed "$VITE_BROWSER_STREAMING_MODE")
|
||||
ESC_ENABLE_LOG_ARTIFACTS=$(escape_sed "$VITE_ENABLE_LOG_ARTIFACTS")
|
||||
ESC_ENABLE_CODE_BLOCK=$(escape_sed "$VITE_ENABLE_CODE_BLOCK")
|
||||
ESC_ENABLE_2FA_NOTIFICATIONS=$(escape_sed "$VITE_ENABLE_2FA_NOTIFICATIONS")
|
||||
|
||||
# Replace placeholder strings in pre-built JS and HTML files with actual runtime values
|
||||
find /app/dist \( -name "*.js" -o -name "*.html" \) -exec sed -i \
|
||||
-e "s|__VITE_API_BASE_URL_PLACEHOLDER__|${ESC_API_BASE_URL}|g" \
|
||||
-e "s|__VITE_WSS_BASE_URL_PLACEHOLDER__|${ESC_WSS_BASE_URL}|g" \
|
||||
-e "s|__VITE_ARTIFACT_API_BASE_URL_PLACEHOLDER__|${ESC_ARTIFACT_API_BASE_URL}|g" \
|
||||
-e "s|__SKYVERN_API_KEY_PLACEHOLDER__|${ESC_SKYVERN_API_KEY}|g" \
|
||||
-e "s|__VITE_BROWSER_STREAMING_MODE_PLACEHOLDER__|${ESC_BROWSER_STREAMING_MODE}|g" \
|
||||
-e "s|__VITE_ENABLE_LOG_ARTIFACTS_PLACEHOLDER__|${ESC_ENABLE_LOG_ARTIFACTS}|g" \
|
||||
-e "s|__VITE_ENABLE_CODE_BLOCK_PLACEHOLDER__|${ESC_ENABLE_CODE_BLOCK}|g" \
|
||||
-e "s|__VITE_ENABLE_2FA_NOTIFICATIONS_PLACEHOLDER__|${ESC_ENABLE_2FA_NOTIFICATIONS}|g" \
|
||||
{} +
|
||||
|
||||
# Sanity check: ensure ALL placeholders were actually replaced.
|
||||
# If Vite ever splits/mangles a placeholder string, sed will silently
|
||||
# succeed while leaving the placeholder in the bundle — catch that here.
|
||||
# Scoped to *.js and *.html (the files sed modifies above) so unmodified
|
||||
# sourcemaps don't trigger false positives.
|
||||
if grep -rqE --include='*.js' --include='*.html' \
|
||||
'__VITE_[A-Z0-9_]+_PLACEHOLDER__|__SKYVERN_API_KEY_PLACEHOLDER__' /app/dist/; then
|
||||
echo "ERROR: Placeholder replacement incomplete in dist/. Unreplaced placeholders:" >&2
|
||||
grep -rhoE --include='*.js' --include='*.html' \
|
||||
'__VITE_[A-Z0-9_]+_PLACEHOLDER__|__SKYVERN_API_KEY_PLACEHOLDER__' /app/dist/ \
|
||||
| sort -u | sed 's/^/ /' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Starting servers..."
|
||||
|
||||
# Ensure both servers are cleaned up if either one exits unexpectedly
|
||||
trap 'kill $(jobs -p) 2>/dev/null; wait' EXIT
|
||||
|
||||
# Start the local server (serves pre-built static assets) and artifact server
|
||||
node localServer.js &
|
||||
node artifactServer.js &
|
||||
wait
|
||||
|
||||
# Exit as soon as either server terminates so the container restarts promptly.
|
||||
# `wait -n` requires bash 4.3+; the base image (node:24.14-slim / Debian 12)
|
||||
# ships bash 5.2 — revisit if the base image is ever swapped for one with an
|
||||
# older bash (e.g. alpine's ash, which doesn't support `wait -n` at all).
|
||||
wait -n
|
||||
exit $?
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue