Task 1

Random City

Build a tiny full-stack app: a page with one button, and a backend that answers with a random city name. Package it as a Docker container, push it to GitHub, and submit the repository here. Our server builds it, runs it in a sandbox, drives it with a real browser and gives you a verdict.

1. What to build

  1. Frontend. The page served at / has a <button id="test"> and a <span id="out">. Use any framework you like, or none.
  2. On click, the page calls your backend with GET /cities.
  3. Backend. GET /cities returns the name of a random city, picked fresh on every call from a list of at least 5 cities.
  4. Display. The city name from the response is shown as the text of #out.

2. API contract

GET /cities HTTP/1.1

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: no-store

{"city": "Nairobi"}
  • Status 200, Content-Type: application/json, body is a JSON object with a non-empty string city (max 100 characters).
  • The answer must be random and must not be cacheable. A browser must not be able to reuse an earlier answer for a later click.
  • Other methods on /cities (for example POST) should be rejected with 405.
  • The frontend must call it with a relative URL (/cities). Both frontend and backend are served from the same origin and port.

3. Container contract

DockerfileA file named Dockerfile at the repository root. The whole app (frontend and backend) runs from this one image.
PortListen on 0.0.0.0 at the port in the PORT environment variable (always 8080). localhost-only binds are unreachable.
HealthGET /healthz must return 200 within 60 seconds of the container starting.
NetworkThe build step has internet access (install your dependencies there). At runtime there is no outbound network. Your app cannot call external APIs, and the page may not load CDN scripts or fonts.
FilesystemRead-only root. /tmp and /data are writable (64 MB each, wiped on exit).
Resources512 MB RAM, 1 CPU, 256 processes. Linux capabilities are dropped, so run as a non-root user if you can.
LimitsRepository up to 300 MB, build up to 10 minutes, image up to 1 GB.

Minimal Dockerfile shape

# Any base image works: node, python, go, java...
FROM node:24-alpine
WORKDIR /app
COPY . .
# Install dependencies at build time (the build has internet access)
RUN npm ci --omit=dev
USER node
ENV PORT=8080
EXPOSE 8080
# Your server must answer "/", "/cities" and "/healthz"
CMD ["node", "server.js"]

4. How to submit

  1. Push your project to a GitHub repository.
  2. Register your team (once) and log in.
  3. Open Submit, paste the repository URL, optionally a branch or tag (default branch if empty).
  4. Private repository? Create a fine-grained personal access token limited to that one repository with "Contents: Read-only" and paste it in the token field. It is encrypted at rest and deleted as soon as judging finishes.
  5. Watch the live status page. You can resubmit after each result (short cooldown applies, one submission at a time per team).

5. How you are judged

Every submission goes through the same automated pipeline:

  1. Clone your repository at its latest commit (the commit hash is recorded with your result).
  2. Build the Docker image.
  3. Run it in an isolated sandbox with no outbound network.
  4. Test the API directly: 30 calls to /cities check status, payload shape, headers, speed and randomness.
  5. Test the UI with a real browser (Chromium). The judge clicks #test repeatedly while a monitoring proxy records every request that leaves the page and every answer your server gives. It verifies that each click really produces a fresh request to your server, that the response reaches the browser unchanged, and that #out shows what the server actually sent. It also replaces a response in flight to confirm your page renders whatever it receives, so hard-coded or locally generated answers fail.
  6. Score the checks below and issue a verdict.

Verdicts

PASSEDEvery required check passed. Your score shows how many optional checks passed too.
FAILEDYour app ran, but at least one required check failed.
START_FAILEDThe container exited, crashed, ran out of memory, or never answered /healthz.
BUILD_FAILEDNo Dockerfile at the root, docker build failed, or a limit was exceeded.
CLONE_FAILEDWe couldn't fetch the repository (bad URL or branch, or missing token for a private repo).
ERRORA problem on our side. Resubmit, and tell an organizer if it repeats.

Scoring rubric

Total 100 points. Checks marked required must all pass for a PASSED verdict.

CheckWhat we verifyPoints
Container
Health endpoint requiredGET /healthz returns 200 within 60 seconds of the container starting.4
Backend
GET /cities succeeds requiredGET /cities returns HTTP 200 every time.5
JSON payload requiredContent-Type is application/json and the body is {"city": "<non-empty string>"}.8
Random city requiredAcross 30 calls the API returns at least 3 distinct cities.6
Not cacheableResponses do not allow caching (no positive max-age, s-maxage, immutable or future Expires).4
Fast responses95th percentile response time is under 1 second.2
Rejects non-GETPOST /cities is rejected with 404 or 405 (405 preferred).2
Frontend
Required elements requiredExactly one <button id="test"> and one <span id="out"> exist on the page at /.8
Every click sends a request requiredEach click makes the browser send exactly one fresh GET /cities request to your own origin. Cached responses, service workers, missing requests and request storms fail.12
Shows the server's answer requiredAfter each click, #out shows exactly the city the server sent in that response.12
Renders what it receives requiredWhen the judge swaps the network response for a value your server never produces, #out shows that value.10
Result changes between clicksOver repeated clicks, #out shows more than one distinct city.4
Responsive UI#out updates within 2 seconds of each click.2
Same-origin requests onlyThe page only talks to its own origin. No third-party APIs, CDNs or absolute URLs to other hosts.3
No page errorsNo uncaught exceptions or failed requests while using the page.3
Traffic
Server received the clicks requiredA monitoring proxy in front of your server sees one GET /cities per click, answered with HTTP 200.10
Payload reaches the browser intactThe body your server sent (seen at the proxy) is byte-for-byte what the browser received.5

6. Common mistakes

Fair play. Submissions run in a sandbox and are judged by their observable behavior. Attempts to detect or tamper with the judge, escape the sandbox or attack the portal lead to disqualification.

Submit your solution