Skip to content

Repository files navigation

Zerops Workshop Q&A

Live workshop Q&A app built on Zerops — participants ask questions from their phones, facilitators moderate and answer from a shared queue. Includes open registration, role-based access (participant, moderator, admin), QR join flow, PostgreSQL, and Next.js 16 SSR on the Zerops platform.

Workshop demo for Zerops and ZCP. Source: kriszerops/zerops-workshop.

Integration Guide

1. Adding zerops.yaml

The main application configuration file you place at the root of your repository, it tells Zerops how to build, deploy and run your application.

zerops:
  # 'prod' compiles an optimized Next.js standalone bundle for
  # deployment. 'dev' deploys full source so you can SSH in and
  # run the Next.js dev server interactively.
  - setup: prod
    build:
      base: nodejs@22

      buildCommands:
        # npm ci installs exact locked versions - reproducible builds.
        - npm ci
        # Bundle migrate.js + pg into a self-contained migrate.cjs
        # so initCommands can run it without NODE_PATH gymnastics.
        # pg is traced by @vercel/nft into .next/standalone/node_modules
        # but migrate.js runs before the server starts, so bundling is
        # the cleanest solution.
        - node scripts/bundle-migrate.mjs
        # prebuild script (generate-build-info.js) writes version +
        # timestamp to build-info.json, then next build compiles the
        # standalone bundle with @vercel/nft import tracing.
        - npm run build

      # Next.js standalone bundles all runtime dependencies via
      # @vercel/nft - no node_modules needed at runtime. Static
      # assets and public dir must be deployed alongside.
      deployFiles:
        - .next/standalone
        - .next/static
        - public
        - build-info.json
        - migrate.cjs

      cache:
        # Do NOT cache .next/cache - Zerops cache restoration causes
        # EACCES permission errors on subsequent builds.
        - node_modules

    # Readiness check verifies the new container responds before
    # the project balancer routes traffic to it.
    deploy:
      readinessCheck:
        httpGet:
          port: 3000
          path: /

    run:
      base: nodejs@22

      initCommands:
        # zsc execOnce ensures migration runs exactly once per
        # deploy version across all containers - prevents race
        # conditions when minContainers > 1.
        # migrate.cjs is esbuild-bundled with pg included, so it
        # runs without any external node_modules.
        - zsc execOnce ${appVersionId} -- node migrate.cjs

      ports:
        - port: 3000
          httpSupport: true

      envVariables:
        NODE_ENV: production
        DB_NAME: db
        # Referencing variables: ${hostname_key} resolves to
        # the value generated by Zerops for the 'db' service.
        DB_HOST: ${db_hostname}
        DB_PORT: ${db_port}
        DB_USER: ${db_user}
        DB_PASS: ${db_password}

      # Standalone server.js reads PORT env var automatically.
      start: node .next/standalone/server.js

  - setup: dev
    build:
      base: nodejs@22
      # Ubuntu gives a richer toolset for interactive SSH work
      # (git, curl, editors) compared to Alpine default.
      os: ubuntu

      buildCommands:
        # npm install (not npm ci) - dev may lack a lock file or
        # need flexible resolution during active development.
        - npm install

      # Deploy the full working directory so the developer has
      # source code, node_modules, and config ready on SSH entry.
      deployFiles: ./

      cache:
        - node_modules

    run:
      base: nodejs@22
      os: ubuntu

      initCommands:
        # Migration runs on first deploy so the database is ready
        # when the developer SSHs in. Dev has node_modules deployed
        # alongside source, so migrate.js uses pg from node_modules.
        - zsc execOnce ${appVersionId} -- node migrate.js

      ports:
        - port: 3000
          httpSupport: true

      envVariables:
        NODE_ENV: development
        DB_NAME: db
        DB_HOST: ${db_hostname}
        DB_PORT: ${db_port}
        DB_USER: ${db_user}
        DB_PASS: ${db_password}

      # zsc noop keeps the container alive. The developer starts
      # `npm run dev` manually via SSH.
      start: zsc noop --silent

2. Key Configuration Points

Standalone mode (output: 'standalone' in next.config.ts): Next.js traces all imports at build time using @vercel/nft and bundles only the required modules into .next/standalone/. No node_modules directory is needed at runtime — the standalone folder is self-contained.

Three deploy artifacts: Standalone mode does NOT automatically include .next/static/ or public/ inside the standalone folder. All three must be listed separately in deployFiles so the runtime server can find them.

Migration bundling: The migration script (migrate.js) needs the pg driver, which lives inside .next/standalone/node_modules/ at runtime. Rather than using NODE_PATH env var workarounds, we use esbuild in buildCommands to bundle migrate.js + pg into a self-contained migrate.cjs that runs anywhere.

zsc execOnce: Production deployments run multiple containers. Without zsc execOnce ${appVersionId}, every container would run the migration simultaneously — causing race conditions. execOnce ensures exactly one container runs the migration per deploy version; all others wait.

No .next/cache in build cache: Zerops cache restoration sets file ownership that triggers EACCES permission errors when Next.js tries to write to the cache directory on the next build. Only cache node_modules.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages