# Per-repo harbor task Dockerfile for flaredown (rubyforgood, GPL-3). Polyglot symptom tracker: # a backend/ Rails 7.1 API (Ruby 3.2.3, Mongoid 8.1 on MongoDB + Postgres + Redis + Sidekiq) # and an Ember frontend/ (Node 14). Mirrors the explore stack; bakes the workspace + Claude Code # (grader), git-commits a baseline. The app lives in subdirs — gems install in /workspace/backend. # # MongoDB 7.0 (not compose's EOL, arm64-less 4.4.9): Mongoid 8.1.3 + driver 2.20.1 support up to # 7.0, which has native amd64 + aarch64 builds. Same wire protocol; the app is version-agnostic. FROM ruby:3.2.3 ARG TOOLKIT_BUILD_ID=dev RUN apt-get update && apt-get install -y --no-install-recommends \ postgresql postgresql-client libpq-dev \ redis-server \ build-essential pkg-config libyaml-dev \ python3 \ git sudo curl ca-certificates gnupg xz-utils jq procps \ && rm -rf /var/lib/apt/lists/* # MongoDB 7.0 server binary (mongod), arch-aware ubuntu2204 build (runs on bookworm). RUN set -eux; \ arch="$(dpkg --print-architecture)"; \ case "$arch" in amd64) marm=x86_64;; arm64) marm=aarch64;; *) echo "unsupported arch: $arch" >&2; exit 1;; esac; \ ver=7.0.14; \ curl -fsSL "https://fastdl.mongodb.org/linux/mongodb-linux-${marm}-ubuntu2204-${ver}.tgz" -o /tmp/mongo.tgz; \ tar -xzf /tmp/mongo.tgz -C /tmp; \ cp /tmp/mongodb-linux-${marm}-ubuntu2204-${ver}/bin/mongod /usr/local/bin/; \ rm -rf /tmp/mongo.tgz /tmp/mongodb-linux-*; \ mongod --version | head -1 # Node via nvm: 18 (default) + 14 (the Ember client; frontend/.nvmrc = v14.21.3). Pin npm 6 # in the v14 line — the frontend's .npmrc is engine-strict and requires npm 6.x (nvm's 14.21.3 # otherwise bundles npm 7, which fails engine-strict). ENV NVM_DIR=/usr/local/nvm RUN mkdir -p "$NVM_DIR" \ && curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash \ && bash -c '. "$NVM_DIR/nvm.sh" \ && nvm install 18 \ && nvm install 14.21.3 && nvm use 14.21.3 && npm install -g npm@6.14.18 \ && nvm alias default 18' \ && for b in node npm npx; do ln -sf "$NVM_DIR"/versions/node/v18.*/bin/"$b" /usr/local/bin/"$b"; done \ && node --version # phantomjs stub — the Ember client's phantomjs-prebuilt@2.1.16 (for `ember test`) has no arm64 # binary and is EOL; a version-reporting stub on PATH makes `npm install` skip the impossible # download so the client's deps install and it can build/serve. `ember test` needs a real # phantomjs (unavailable on arm64 upstream anyway); the rspec verifier doesn't touch the client. RUN printf '#!/bin/bash\n[ "$1" = "--version" ] && { echo "2.1.1"; exit 0; }\nexit 0\n' > /usr/local/bin/phantomjs \ && chmod +x /usr/local/bin/phantomjs # Match backend/Gemfile.lock "BUNDLED WITH 2.5.6". RUN gem install bundler -v 2.5.6 # Postgres trust auth (backend/config/database.yml connects as PG_DATABASE_USERNAME=postgres). RUN PG_VERSION=$(ls /etc/postgresql) \ && printf 'local all all trust\nhost all all 127.0.0.1/32 trust\nhost all all ::1/128 trust\nhost all all 0.0.0.0/0 trust\n' > "/etc/postgresql/${PG_VERSION}/main/pg_hba.conf" \ && echo "listen_addresses='*'" >> "/etc/postgresql/${PG_VERSION}/main/postgresql.conf" # Install Claude Code globally (grader runs `claude`); hard-gate on presence — a missing grader # CLI silently zeros every reward, so a broken image must never be cached. ARG CLAUDE_CODE_MIN=2.1.251 RUN for i in 1 2 3; do \ if curl -fsSL https://claude.ai/install.sh -o /tmp/claude-install.sh && bash /tmp/claude-install.sh; then break; fi; \ echo "WARNING: claude install attempt $i failed; retrying in 5s" >&2; sleep 5; \ done; \ rm -f /tmp/claude-install.sh; \ for p in /root/.claude-code/claude /root/.local/bin/claude "$(find /root -name claude -type f 2>/dev/null | head -1)"; do \ [ -n "$p" ] && [ -x "$p" ] && ln -sf "$p" /usr/local/bin/claude && break; \ done; \ command -v claude >/dev/null 2>&1 || { echo "FATAL: claude CLI not installed — the grader needs it" >&2; exit 1; }; \ _v="$(claude --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)"; \ [ "$(printf '%s\n%s\n' "$CLAUDE_CODE_MIN" "$_v" | sort -V | head -1)" = "$CLAUDE_CODE_MIN" ] \ || { echo "FATAL: claude $_v is older than $CLAUDE_CODE_MIN, the minimum the grader needs" >&2; exit 1; }; \ echo "claude $_v installed at $(command -v claude)" USER root # --- Playwright + Chromium, when the task opts in ---------------------------- # Installed only when task.toml sets `[metadata] browser = true`. A Dockerfile cannot read # task.toml, so build-workspace.sh writes that answer to environment/browser-optin. # Self-contained under /opt — the member's own runtime is untouched. ENV PLAYWRIGHT_BROWSERS_PATH=/opt/ms-playwright COPY browser-optin /tmp/browser-optin RUN set -eu; \ if [ "$(cat /tmp/browser-optin)" != "1" ]; then echo "browser: task did not opt in; skipping Playwright"; exit 0; fi; \ set -x; \ apt-get update -qq; \ apt-get install -y -qq --no-install-recommends \ xz-utils \ libxcomposite1 \ libxdamage1 \ libxfixes3 \ libxrandr2 \ libasound2 \ libatk1.0-0 \ libatk-bridge2.0-0 \ libatspi2.0-0 \ libcups2 \ libdbus-1-3 \ libgbm1 \ libnspr4 \ libnss3 \ libxkbcommon0 \ libpango-1.0-0 \ libcairo2 \ libxshmfence1 \ libx11-xcb1 \ libxcb-dri3-0 \ libdrm2; \ rm -rf /var/lib/apt/lists/*; \ arch="$(dpkg --print-architecture)"; \ case "$arch" in amd64) nodearch=x64;; arm64) nodearch=arm64;; *) echo "unsupported arch: $arch" >&2; exit 1;; esac; \ curl -fsSL "https://nodejs.org/dist/v20.19.5/node-v20.19.5-linux-${nodearch}.tar.xz" -o /tmp/pw-node.tar.xz; \ mkdir -p /opt/pw-node; \ tar -xJf /tmp/pw-node.tar.xz -C /opt/pw-node --strip-components=1; \ rm /tmp/pw-node.tar.xz; \ export npm_config_prefix=/opt/pw-node PATH="/opt/pw-node/bin:$PATH"; \ /opt/pw-node/bin/npm install -g playwright@1.56.0; \ test -d /opt/pw-node/lib/node_modules/playwright; \ /opt/pw-node/bin/node /opt/pw-node/lib/node_modules/playwright/cli.js install chromium; \ printf '#!/bin/sh\nNODE_PATH=/opt/pw-node/lib/node_modules exec /opt/pw-node/bin/node "$@"\n' > /usr/local/bin/pw; \ chmod +x /usr/local/bin/pw; \ printf 'const{chromium}=require("playwright");(async()=>{const b=await chromium.launch();const p=await b.newPage();await p.setContent("