diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 584f95d..6d2828f 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -58,6 +58,16 @@ jobs: id: login-ecr uses: aws-actions/amazon-ecr-login@v2 + - name: Prepare build env + id: build-env + env: + NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }} + NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY }} + NEXT_PUBLIC_LOGIN_TURNSTILE_SITE_KEY: ${{ secrets.NEXT_PUBLIC_LOGIN_TURNSTILE_SITE_KEY }} + NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN: ${{ secrets.NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN }} + NEXT_PUBLIC_POSTHOG_HOST: ${{ secrets.NEXT_PUBLIC_POSTHOG_HOST }} + run: echo "env_b64=$(env | grep '^NEXT_PUBLIC_' | base64 -w0)" >> "$GITHUB_OUTPUT" + - name: Build image id: build uses: redhat-actions/buildah-build@v2 @@ -65,12 +75,7 @@ jobs: image: ${{ env.CONTAINER_NAME }} tags: latest ${{ github.sha }} containerfiles: ./Dockerfile - build-args: | - NEXT_PUBLIC_SUPABASE_URL=${{ secrets.NEXT_PUBLIC_SUPABASE_URL }} - NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=${{ secrets.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY }} - NEXT_PUBLIC_LOGIN_TURNSTILE_SITE_KEY=${{ secrets.NEXT_PUBLIC_LOGIN_TURNSTILE_SITE_KEY }} - NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN=${{ secrets.NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN }} - NEXT_PUBLIC_POSTHOG_HOST=${{ secrets.NEXT_PUBLIC_POSTHOG_HOST }} + build-args: BUILD_ENV_B64=${{ steps.build-env.outputs.env_b64 }} - name: Push image to ECR id: push diff --git a/Dockerfile b/Dockerfile index 9fd9344..3e86812 100644 --- a/Dockerfile +++ b/Dockerfile @@ -48,16 +48,7 @@ COPY . . ENV NODE_ENV=production -ARG NEXT_PUBLIC_SUPABASE_URL -ARG NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY -ARG NEXT_PUBLIC_LOGIN_TURNSTILE_SITE_KEY -ARG NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN -ARG NEXT_PUBLIC_POSTHOG_HOST -ENV NEXT_PUBLIC_SUPABASE_URL=$NEXT_PUBLIC_SUPABASE_URL -ENV NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=$NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY -ENV NEXT_PUBLIC_LOGIN_TURNSTILE_SITE_KEY=$NEXT_PUBLIC_LOGIN_TURNSTILE_SITE_KEY -ENV NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN=$NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN -ENV NEXT_PUBLIC_POSTHOG_HOST=$NEXT_PUBLIC_POSTHOG_HOST +ARG BUILD_ENV_B64 # Next.js collects completely anonymous telemetry data about general usage. # Learn more here: https://nextjs.org/telemetry @@ -70,7 +61,9 @@ ENV NEXT_PUBLIC_POSTHOG_HOST=$NEXT_PUBLIC_POSTHOG_HOST # This caches the .next/cache directory across builds, but it also prevents # .next/cache/fetch-cache from being included in the final image, meaning # cached fetch responses from the build won't be available at runtime. -RUN if [ -f package-lock.json ]; then \ +RUN BUILD_ENV="$(printf '%s' "$BUILD_ENV_B64" | base64 -d)" \ + && printf '%s\n' "$BUILD_ENV" > .env.production.local \ + && if [ -f package-lock.json ]; then \ npm run build; \ elif [ -f yarn.lock ]; then \ corepack enable yarn && yarn build; \ diff --git a/docs/development.md b/docs/development.md index 267611c..08e7677 100644 --- a/docs/development.md +++ b/docs/development.md @@ -17,7 +17,7 @@ by hand. | --------------- | ---------------- | ---------------------------------------------- | -------------------------------------- | ------------------------------------------------------ | | Database schema | **Drizzle** | `lib/db/schema.ts`, `supabase/migrations/` | `db:push`, `db:generate`, `db:migrate` | `drizzle-kit migrate` (pooler `DATABASE_URL`) | | Platform config | **Supabase CLI** | `supabase/config.toml` + root `.env` (secrets) | read at `db:start` (base `[auth]`) | `supabase config push` (merges `[remotes.production]`) | -| App connection | **Env vars** | `.env.local` (generated) | `db:env` | hosting platform dashboard + redeploy | +| App connection | **Env vars** | `.env.local` (generated) | `db:env` | CD bakes `NEXT_PUBLIC_*`; ECS reads SSM | **Drizzle owns schema migrations, not Supabase.** drizzle-kit reads `supabase/migrations/` and tracks applied files in `__drizzle_migrations`. The diff --git a/docs/remote-development.md b/docs/remote-development.md index 03c6365..105acfb 100644 --- a/docs/remote-development.md +++ b/docs/remote-development.md @@ -61,17 +61,34 @@ Update prod URLs in `[remotes.production.auth]` when the production hostname cha ## 3. App connection → remote -Set these on your hosting platform, then redeploy. Values are in -[Keys in Notion](https://app.notion.com/p/Keys-38124ca0c81b80ffac62f65acb442613): - -| Variable | Value (Dashboard → Settings → API) | -| -------------------------------------- | ---------------------------------- | -| `NEXT_PUBLIC_SUPABASE_URL` | remote project URL | -| `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY` | remote publishable key | -| `DATABASE_URL` | remote Transaction pooler string | -| `RESUMES_BUCKET` | AWS S3 bucket name (SSM) | -| `RESUMES_ACCESS_KEY_ID` | AWS IAM access key (SSM) | -| `RESUMES_SECRET_ACCESS_KEY` | AWS IAM secret key (SSM) | +Values are in +[Keys in Notion](https://app.notion.com/p/Keys-38124ca0c81b80ffac62f65acb442613). + +`NEXT_PUBLIC_*` values are GitHub Actions secrets. CD passes them into the image +as one `BUILD_ENV_B64` build arg +([`.github/workflows/cd.yml`](../.github/workflows/cd.yml)); the +[Dockerfile](../Dockerfile) writes that to `.env.production.local` before +`next build`. Next inlines them, so they are not set on the ECS task. Changing +one requires a new image build. Add a variable by listing it on the Prepare +build env step. + +| Variable | Value | +| -------------------------------------- | ---------------------- | +| `NEXT_PUBLIC_SUPABASE_URL` | remote project URL | +| `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY` | remote publishable key | +| `NEXT_PUBLIC_LOGIN_TURNSTILE_SITE_KEY` | Turnstile site key | +| `NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN` | PostHog project token | +| `NEXT_PUBLIC_POSTHOG_HOST` | PostHog host | + +Server secrets stay in SSM and are injected by +[`task-definition.json`](../task-definition.json) when the task starts: + +| Variable | Value | +| --------------------------- | -------------------------------- | +| `DATABASE_URL` | remote Transaction pooler string | +| `RESUMES_BUCKET` | AWS S3 bucket name | +| `RESUMES_ACCESS_KEY_ID` | AWS IAM access key | +| `RESUMES_SECRET_ACCESS_KEY` | AWS IAM secret key | Set `RESUMES_REGION` to `us-east-2` in production (or omit it — that is the default). Local resume storage is seeded by `supabase/seed.sql` only — there are no @@ -79,9 +96,6 @@ Local resume storage is seeded by `supabase/seed.sql` only — there are no cannot create buckets on the remote Supabase project. Remote schema promotion uses drizzle-kit migrate, not `supabase db reset`, so `seed.sql` never runs against production. -`NEXT_PUBLIC_*` vars are **inlined at build time** — changing them requires a -rebuild/redeploy. - ## Command reference | Command | Does | diff --git a/task-definition.json b/task-definition.json index 0812f35..e9bd4d7 100644 --- a/task-definition.json +++ b/task-definition.json @@ -17,14 +17,6 @@ "mountPoints": [], "volumesFrom": [], "secrets": [ - { - "name": "NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY", - "valueFrom": "arn:aws:ssm:us-east-2:AWS_ACCOUNT_ID:parameter/mhacks-secrets/NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY" - }, - { - "name": "NEXT_PUBLIC_SUPABASE_URL", - "valueFrom": "arn:aws:ssm:us-east-2:AWS_ACCOUNT_ID:parameter/mhacks-secrets/NEXT_PUBLIC_SUPABASE_URL" - }, { "name": "RESUMES_BUCKET", "valueFrom": "arn:aws:ssm:us-east-2:AWS_ACCOUNT_ID:parameter/mhacks-secrets/RESUMES_BUCKET" @@ -64,18 +56,6 @@ { "name": "LOGIN_TURNSTILE_SECRET_KEY", "valueFrom": "arn:aws:ssm:us-east-2:AWS_ACCOUNT_ID:parameter/mhacks-secrets/LOGIN_TURNSTILE_SECRET_KEY" - }, - { - "name": "NEXT_PUBLIC_LOGIN_TURNSTILE_SITE_KEY", - "valueFrom": "arn:aws:ssm:us-east-2:AWS_ACCOUNT_ID:parameter/mhacks-secrets/NEXT_PUBLIC_LOGIN_TURNSTILE_SITE_KEY" - }, - { - "name": "NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN", - "valueFrom": "arn:aws:ssm:us-east-2:AWS_ACCOUNT_ID:parameter/mhacks-secrets/NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN" - }, - { - "name": "NEXT_PUBLIC_POSTHOG_HOST", - "valueFrom": "arn:aws:ssm:us-east-2:AWS_ACCOUNT_ID:parameter/mhacks-secrets/NEXT_PUBLIC_POSTHOG_HOST" } ], "logConfiguration": {