DigitalOcean · Opus 5.5 · medium effort · HIPAA organization · trial 1

Static Content Site on DigitalOcean

Success

Checks passed16/16
Human steps37 min of a person's time
Deploy without a human Failed
Deploy with a human Deployed
Security & compliance91/100
Stack fit81/100
Build2m 02s
Deploy2m 07s
Tokens1.10M
Agent cost$0.75
Hosting / month$5.00 light use$5.00 busy

Deployed to https://adb-qzsmi-static-cw3ti.ondigitalocean.app. Every deployment is deleted after it's verified and reviewed.

Screenshots

The home page
The home page · https://adb-qzsmi-static-cw3ti.ondigitalocean.app/
The contact page after the verifier submitted the form
The contact page after the verifier submitted the form · https://adb-qzsmi-static-cw3ti.ondigitalocean.app/contact

Technologies

Runtime
Node.js 22
Frameworks
Node.js http module (built in)
Packaging
Docker (node:22-alpine)App spec (.do/app.yaml)
Hosting
App Platform service (apps-s-1vcpu-0.5gb)DigitalOcean Container Registry (starter)

Named from the app's dependencies, Dockerfile and config files, and the cloud resources it created.

Checks

Black-box checks against the live URL. A cell succeeds when every required check passes. The app's spec

ResultCheckDetails
passed GET / answers 2xx within 1 min ready
passed The app is served over HTTPS with a valid certificate https
passed The app is public: no redirect to another host such as a login page public_access
passed GET / returns HTML page_home
passed GET /about returns HTML page_about
passed GET /contact returns HTML page_contact
passed Home has an <h1> containing 'Northwind Robotics' home_content
passed About has a heading containing 'About' about_content
passed Contact has a form with name, email and message fields contact_form
passed Every page has a <title> and a viewport meta tag head_tags optional
passed Home links to /about and /contact navigation optional
passed POST /api/contact with valid input returns {ok: true} contact_api_valid
passed POST /api/contact rejects a bad email and an empty message with 400 contact_api_validation
passed Unknown paths return 404 not_found
passed Submitting the /contact form in a browser posts to /api/contact without leaving the page browser_contact_form
passed Pages load in a browser without uncaught JavaScript errors browser_no_errors optional

Human steps

Stack fit

The same review rated the stack the agent chose, judged against the app's requirements rather than a preferred stack.

A zero-dependency Node.js web service on App Platform is a simple, correct fit for three static pages and one JSON endpoint. A static site plus a Function would have matched the platform better, but the agent documented why it chose against it.

CriterionWeightScoreWhy
Fit for the job 40 9/10 A built-in http server meets the exact clean-path, 404 and same-origin JSON endpoint requirements with no database, as the spec asks. The contact form uses fetch with in-page confirmation.
Proportion 30 8/10 About 100 lines, no dependencies and no build step is very lean. Running a container to serve mostly static pages is slightly more than strictly needed.
Platform fit 30 7/10 An always-on $5/mo container serves static files that App Platform could host for free as a static site plus a Function. The agent weighed that option in STACK.md and rejected it reasonably, since it needed a connected git repo, but it doesn't make the most of the platform.

Security & compliance review

An independent Claude Code session reviewed the source, the agent's sessions and the live deployment against a fixed rubric (claude-opus-5-5, high effort).

A small, zero-dependency Node.js server runs as a non-root container on a paid DigitalOcean App Platform Basic instance. It handles secrets cleanly, exposes nothing beyond its allow-listed pages and assets, and redirects HTTP to HTTPS. The remaining weaknesses are minor: the base and deployed images aren't pinned by digest, the app sets no HSTS/CSP/frame-protection headers, and the contact endpoint has no rate limiting.

CriterionWeightScoreWhy
Secrets handling 25 10/10 The app needs no secrets. The DigitalOcean token was only read from the environment by doctl, never printed in the transcripts, and .env is excluded by both .gitignore and .dockerignore.
Deployment provenance 20 7/10 The image was built locally from the reviewed source and pushed to a DOCR registry in the owner's account, and it has no npm dependencies. But the base image is the floating tag node:22-alpine, and App Platform deploys the mutable :latest tag, not a digest.
Terms compliance 15 10/10 A commercial app on a paid App Platform Basic instance ($5/mo) plus the free DOCR starter tier. As far as I know, both plans allow commercial use and the instance stays up instead of sleeping.
Least privilege 15 10/10 The container runs as the non-root node user. The only public component is the single web service, and there are no data stores or extra resources.
Transport security 10 8/10 HTTPS is served with a valid Google Trust Services certificate and HTTP returns 301 to HTTPS. The app sets X-Content-Type-Options, but it sends no HSTS, CSP or frame protection, even though its own server could have set them.
Attack surface 10 10/10 Only the allow-listed pages and assets are served. Source files, .env, .git, package.json and Dockerfile all return the generic 404 page, with no directory listings or stack traces.
Application security 5 8/10 Input is validated strictly by type and email regex, bodies are capped at 16 KB, JSON errors are handled, non-POST methods get 405, and the client renders responses with textContent, so there is no XSS. Minor gaps: no rate limiting on the public endpoint, and submitter emails are written to logs.

Findings

Cloud resources and cost

TrafficPer monthHow it was priced
Light use
Barely used: a few visits a week, about 2,000 requests a month. A scale-to-zero app is awake about 10 hours a month, idle timeouts included.
$5.00fixed monthly prices; 0.0373 GiB out of a 50 GiB allowance
Busy
A public app that crawlers, uptime monitors and visitors hit around the clock: one request a second on average, so it never goes idle.
$5.00fixed monthly prices; 49 GiB out of a 50 GiB allowance

Resources the agent created

KindNamePlanList price / monthPricing
appadb-qzsmi-staticadb-qzsmi-static-web: apps-s-1vcpu-0.5gb x 1$5.00adb-qzsmi-static-web: apps-s-1vcpu-0.5gb x 1
registryadb-qzsmi-staticstarter$0.00Container Registry starter tier (one per account)

The agent estimated $5.00 a month.

Code

Code
458 lines in 19 files (JavaScript 193, HTML 170, Shell 53, CSS 34, Markdown 22, JSON 22, YAML 15, Docker 8)
Dependencies
0 npm, 0 Python
Deploy config
Dockerfile .dockerignore .do/app.yaml

The agent's sessions

PhaseStatusTimeTurnsTokensCostShell commands
Buildcompleted2m 02s12459.9K$0.3911
Deploycompleted2m 07s8363.7K$0.176

The agent's deploy summary

The site is live at https://adb-qzsmi-static-cw3ti.ondigitalocean.app, and every requirement in TASK.md passes when tested through that URL.

What's deployed:

  • App Platform app adb-qzsmi-static: one web service, adb-qzsmi-static-web, on the smallest Basic size (apps-s-1vcpu-0.5gb, 1 instance, NYC). It runs the Node server as a Docker container, and App Platform provides the HTTPS. This costs about $5/month.
  • Container registry adb-qzsmi-static: free starter tier, holding the app image. Deploying from an image meant no GitHub account had to be connected.

Tested through the public URL:

  • /, /about and /contact return 200 as HTML, each with a title, a viewport tag and links to the other pages.
  • Unknown paths return 404.
  • POST /api/contact returns 200 for valid input and 400 with an error message for a missing name, a bad email or an empty message.
  • In headless Chrome at phone width, the contact form submitted and showed its confirmation without reloading the page.

What went wrong: the first deploy run reported every check as failed. The app's new address wasn't reachable yet when the checks ran; a minute later all checks passed. I changed scripts/deploy.sh to wait for the site to respond before testing.

No new credentials were created, so there's no .env file. No steps needed a person, so human_steps in deployment.json is empty. To redeploy, run scripts/deploy.sh.

6 shell commands during deploy
  1. 4s cd /home/runner/work/_temp/adb-qzsmi-static; which doctl docker; doctl account get --format Email,Status,DropletLimit 2>&1 | tail -1 | sed 's/^[^ ]*@[^ ]* /<email> /'; doctl registry get 2>&1 | tail -
  2. 9s scripts/deploy.sh > /tmp/deploy.log 2>&1; echo exit=$?; grep -vE 'Pushed|Waiting|Preparing|Layer|^#' /tmp/deploy.log | tail -30
  3. 1m 31s cd /home/runner/work/_temp/adb-qzsmi-static; A=4473062e-8898-4e9d-bbc6-a2a27c4952c9; doctl apps list-deployments $A --format ID,Phase,Progress; for i in $(seq 1 30); do c=$(curl -s -o /dev/null -w '%{
  4. 1m 42s sed -i 's#^./scripts/smoke.sh "\$URL"#for _ in $(seq 1 30); do [ "$(curl -s -o /dev/null -w "%{http_code}" "$URL/")" = 200 ] \&\& break; sleep 10; done # wait for DNS/TLS\n&#' scripts/deploy.sh && ta
  5. 1m 50s bash -n scripts/deploy.sh && doctl registry get --format Name,StorageUsageBytes --no-header && doctl apps get 4473062e-8898-4e9d-bbc6-a2a27c4952c9 --format ID,DefaultIngress,ActiveDeployment.ID --no-h
  6. 2m 01s cat > deployment.json <<'EOF'

Raw data