Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.git
.github
.next
node_modules
.env*
*.md
scripts
28 changes: 20 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,24 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
# The version comes from packageManager in package.json, the same Bun the
# image (.nixpacks/Dockerfile) pins.
- uses: oven-sh/setup-bun@v2
with:
node-version: 24
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm test
- run: pnpm typecheck
- run: pnpm build
bun-version-file: package.json
- run: bun install --frozen-lockfile
- run: bun run test
- run: bun run typecheck
- run: bun run build
# The production server must boot under Bun and answer.
- name: Standalone server answers under Bun
run: |
cp -r public .next/standalone/
cp -r .next/static .next/standalone/.next/
(cd .next/standalone && PORT=3000 HOSTNAME=127.0.0.1 bun server.js > /tmp/server.log 2>&1 &)
for i in $(seq 1 30); do
code=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3000/healthz || true)
[ "$code" = 200 ] && { echo "/healthz -> $code"; exit 0; }
sleep 1
done
cat /tmp/server.log; exit 1
79 changes: 44 additions & 35 deletions .nixpacks/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,35 +1,44 @@
FROM ghcr.io/railwayapp/nixpacks:ubuntu-1745885067

ENTRYPOINT ["/bin/bash", "-l", "-c"]
WORKDIR /app/


COPY .nixpacks/nixpkgs-23f9169c4ccce521379e602cc82ed873a1f1b52b.nix .nixpacks/nixpkgs-23f9169c4ccce521379e602cc82ed873a1f1b52b.nix
RUN nix-env -if .nixpacks/nixpkgs-23f9169c4ccce521379e602cc82ed873a1f1b52b.nix && nix-collect-garbage -d


ARG CI NIXPACKS_METADATA NODE_ENV NPM_CONFIG_PRODUCTION
ENV CI=$CI NIXPACKS_METADATA=$NIXPACKS_METADATA NODE_ENV=$NODE_ENV NPM_CONFIG_PRODUCTION=$NPM_CONFIG_PRODUCTION

# setup phase
# noop

# install phase
ENV NIXPACKS_PATH=/app/node_modules/.bin:$NIXPACKS_PATH
COPY . /app/.
RUN --mount=type=cache,id=bk0YUPar8-/root/local/share/pnpm/store/v3,target=/root/.local/share/pnpm/store/v3 npm install -g corepack@0.24.1 && corepack enable
RUN --mount=type=cache,id=bk0YUPar8-/root/local/share/pnpm/store/v3,target=/root/.local/share/pnpm/store/v3 pnpm i --frozen-lockfile

# build phase
COPY . /app/.
RUN --mount=type=cache,id=bk0YUPar8-next/cache,target=/app/.next/cache --mount=type=cache,id=bk0YUPar8-node_modules/cache,target=/app/node_modules/.cache pnpm run build


RUN printf '\nPATH=/app/node_modules/.bin:$PATH' >> /root/.profile


# start
COPY . /app

CMD ["pnpm run start"]

# syntax=docker/dockerfile:1
#
# launch.anapp.now, run on Bun.
#
# This file lives at .nixpacks/Dockerfile because that is the path dev2's compose
# stack builds (cli-tools dev2/dev2-site provision records it per site, and
# re-provisioning keeps it). It used to be nixpacks output (Node + pnpm); it is now
# a hand-written Bun image, so do not regenerate it with nixpacks.
#
# Contract with /home/anthony/www/launch.anapp.now on dev2, unchanged from the Node
# image: listens on 3000 inside the container (compose maps 127.0.0.1:APP_PORT),
# answers /healthz for the deploy health check, reads its env from app.env at run
# time. No build args.

FROM oven/bun:1.4.0-slim AS deps
WORKDIR /app
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile

FROM deps AS build
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
# `bun --bun next build` (the build script): Next runs on Bun, not Node.
RUN bun run build

FROM oven/bun:1.4.0-slim AS runtime
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
ENV PORT=3000
# Docker sets HOSTNAME to the container id and Next's standalone server binds to it
# literally; it must listen on every interface for the published port to reach it.
ENV HOSTNAME=0.0.0.0
# Standalone output: server.js plus only the dependencies it imports.
COPY --from=build --chown=bun:bun /app/.next/standalone ./
COPY --from=build --chown=bun:bun /app/.next/static ./.next/static
COPY --from=build --chown=bun:bun /app/public ./public
# The oven/bun image ships a non-root `bun` user.
USER bun
EXPOSE 3000
# 127.0.0.1, not localhost: localhost resolves ::1 first and Next binds IPv4 only.
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
CMD bun -e "fetch('http://127.0.0.1:'+(process.env.PORT||3000)+'/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
CMD ["bun", "server.js"]
1 change: 0 additions & 1 deletion .nixpacks/build.sh

This file was deleted.

21 changes: 0 additions & 21 deletions .nixpacks/nixpkgs-23f9169c4ccce521379e602cc82ed873a1f1b52b.nix

This file was deleted.

13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@
Top-of-funnel campaign site for [Chovy](https://chovy.com). One job: get a
visitor to type an app idea and carry it straight into Chovy without retyping.

Next.js 16, TypeScript, pnpm, Node 24. No database: ideas and funnel events are
Next.js 16, TypeScript, Bun (package manager and runtime). No database: ideas and funnel events are
forwarded server-to-server to Chovy's campaign API (`chovy-ai` repo,
`/api/campaign/*`), which is where the experiment report lives.

## Run

```
pnpm install
bun install
cp .env.example .env # fill CHOVY_CAMPAIGN_SECRET (same value as Chovy's CAMPAIGN_SECRET)
pnpm dev
bun dev
```

`pnpm test` runs the unit tests (allocation, cookie, validation) with `node --test`.
`pnpm build` type-checks and builds. `pnpm creatives:placeholders` regenerates the
`bun run test` runs the unit tests (allocation, cookie, validation) with `bun test`.
`bun run build` type-checks and builds. `bun run creatives:placeholders` regenerates the
stand-in hero images (see below).

## How it fits together
Expand Down Expand Up @@ -65,7 +65,8 @@ side, keyed by the same anonymous session, and reports everything at

## Deploy

Railway service from this repo (Nixpacks, `pnpm start`, health check `/healthz`).
dev2 builds `.nixpacks/Dockerfile` (a hand-written `oven/bun` image running the Next
standalone server with `bun server.js`) on every merge to main; health check `/healthz`.
Variables: see `.env.example`. Point `launch.anapp.now` at the service's domain
and set `SITE_ORIGIN=https://launch.anapp.now`. The apex `anapp.now` should 301
to the campaign host.
Loading
Loading