Skip to content

chore(dev): drop localstack from the dev stack - #81257

Merged
trunk-io[bot] merged 18 commits into
masterfrom
worktree-migrate-off-localstack
Aug 18, 2026
Merged

trunk-io[bot] merged 18 commits into
masterfrom
worktree-migrate-off-localstack

Conversation

@rnegron

@rnegron rnegron commented Aug 11, 2026 •

Copy link
Copy Markdown
Member

Problem

  • LocalStack ended its free Community Edition on 2026-03-23, so CI and the dev stack run a pinned, unpatched emulator (localstack:4.12.0).
  • Its only consumer is the session replay keystore integration test, which needs DynamoDB, KMS, and S3.

Closes #53436

Changes

  • The test now runs on amazon/dynamodb-local, the existing objectstorage service, and an in-process KMS stub that enforces EncryptionContext and KeyId the way real KMS does.
  • The duplicated encryption suite runs once, against MemoryKeyStore. The DynamoDB block keeps only what a real DynamoDB decides: composite key, conditional writes, tombstones.
  • The service is profile-gated in dev and sandbox, and mapped to nodejs_session_replay so replay stacks start it.
  • The rust e2e job stops starting an emulator it never used, and the dead LOCALSTACK_ENABLED variable is gone.
  • -disableTelemetry is set (on by default), and devenv/news.txt tells devs to clear leftover localstack-main containers.
LocalStack 4.12.0 dynamodb-local 3.3.1
License free tier ended free for any use
Image size 475 MB 237 MB
Host port 4566 18010
Healthcheck none port probe
docker.sock mount yes no

Note

The dev SES_ENDPOINT pins to localhost:4566 stay. The SES client passes no explicit credentials, so the dead-port pin is what keeps a local send off real AWS via ambient credentials. Cleanup belongs to the messaging owners.

How did you test this code?

  • CI green, including the trimmed rust job: run 31569603534. The shard log shows dynamodb-main Healthy and the file passing.
  • 26/26 locally against the dev stack; DYNAMODB_TESTS_DISABLED=1 skips the 7 stack-dependent cases.
  • New assertions are mutation-tested: renaming the KMS alias or dropping deleted_at from the tombstone write turns tests red.

Automatic notifications

  • Publish to changelog?

Docs update

None.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

  • Claude Code, directed by Raúl. Skills: /writing-tests, /writing-code-comments, /writing-pr-descriptions, /code-review, /simplify.
  • Review passes fixed: host port 18000 collided with the eval harness live server, CI never waited on objectstorage's bootstrap sentinel, the profile sat in the legacy overlay, and an SES default deletion that would have failed open to real AWS was reverted.

@rnegron rnegron self-assigned this Aug 11, 2026
@trunk-io

trunk-io Bot commented Aug 11, 2026 •

Copy link
Copy Markdown

😎 Merged successfully - details.

@github-actions

github-actions Bot commented Aug 11, 2026 •

Copy link
Copy Markdown
Contributor

🤖 CI report

✅ Bundle size — no change

Uncompressed size of every built .js bundle, compared against the base branch.

Total: 68.31 MiB · no change

No file changed by more than 1000 B.

Posted automatically by build-bundle-size-report · uncompressed bytes from dist-report

✅ Eager graph — within budget

How much code each root ships on the eager path — downloaded and parsed before the surface is interactive. Measured from the esbuild output chunks (post-tree-shake, static imports only); lazy import() / React.lazy chunks are not counted.

Root Eager (shipped) Δ vs base Budget
entry (logged-out pages, app bootstrap)
src/index.tsx
1.28 MiB · 22 files no change ███░░░░░░░ 28.4% of 4.51 MiB
authenticated shell (every logged-in page)
src/scenes/AuthenticatedShell.tsx
8.56 MiB · 3,150 files no change █████████░ 88.2% of 9.71 MiB

🟢 node_modules/monaco-editor/ stays out of src/index.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 node_modules/monaco-editor/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx

Largest files eagerly shipped from src/index.tsx
Size File
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
24.6 KiB ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js
6.3 KiB ../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js
4.5 KiB ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js
3.9 KiB ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js
1.4 KiB ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js
1.3 KiB src/RootErrorBoundary.tsx
912 B ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js
789 B src/scenes/ChunkLoadErrorBoundary.tsx
762 B src/index.tsx
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
Size File
304.7 KiB ../node_modules/.pnpm/posthog-js@1.417.1/node_modules/posthog-js/dist/rrweb.js
267.7 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
246.5 KiB src/taxonomy/core-filter-definitions-by-group.json
243.9 KiB ../node_modules/.pnpm/posthog-js@1.417.1/node_modules/posthog-js/dist/module.js
154.2 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
104.0 KiB src/lib/api.ts
95.0 KiB ../packages/quill/packages/quill/dist/index.js
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
90.6 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js

Posted automatically by check-eager-graph · sizes are eager output bytes (shipped, post-tree-shake) from the esbuild metafile · part of #32479

✅ Toolbar bundle — eager 2.23 MiB within budget

What the toolbar ships to customer pages, measured from the esbuild output (minified, post-tree-shake). The eager set is the entry plus everything statically imported from it — fetched before any feature runs; deferred chunks load lazily. The eager guardrail is 5.72 MiB. Each output file must also stay below 10 MB, where CloudFront stops compressing it. The module boundary is enforced separately by check-toolbar-graph.

Metric Size Δ vs base Budget
Eager (shipped)
entry + static imports
2.23 MiB · 17 files no change ████░░░░░░ 38.9% of 5.72 MiB
Deferred (lazy) 2.09 MiB · 33 files no change n/a — loads on demand
Loader dist/toolbar.js 1.1 KiB no change █░░░░░░░░░ 5.8% of 19.5 KiB
Largest eagerly-shipped chunks
Size File
741.3 KiB dist/toolbar/toolbar-app-7XQF5GZA.css
565.7 KiB dist/toolbar/chunk-chunk-5XQV6DM2.js
484.6 KiB dist/toolbar/chunk-chunk-3A7RSMAG.js
133.6 KiB dist/toolbar/chunk-chunk-RYSZ67CJ.js
131.8 KiB dist/toolbar/chunk-chunk-T5KY5WYR.js
71.2 KiB dist/toolbar/toolbar-app-MBCFR53V.js
69.0 KiB dist/toolbar/chunk-chunk-27JL52RE.js
35.6 KiB dist/toolbar/chunk-chunk-TVGFVNS4.js
20.9 KiB dist/toolbar/chunk-chunk-3D4FXM63.js
12.2 KiB dist/toolbar/chunk-chunk-PIK3PADE.js

Posted automatically by check-toolbar-size · sizes are toolbar output bytes (shipped, post-tree-shake) from the esbuild metafile

✅ Dist folder size — no change

Total size of the built frontend/dist folder (all assets), compared against the base branch.

Total: 1450.28 MiB · no change

✅ Playwright — all passed

All tests passed.

View test results →

✅ Hobby preview — passed

Hobby deployment smoke test passed successfully.


Run 32159456545

@trunk-io

trunk-io Bot commented Aug 11, 2026 •

Copy link
Copy Markdown

Static Badge   Static Badge   Static Badge

Failed Test Failure Summary Logs
a_successors_init_aborts_the_predecessors_open_window The test failed due to an assertion that expected a different value, indicating a mismatch in the expected state of the record's successor. Logs ↗︎

View Full Report ↗︎ ⋅ Docs

@rnegron
rnegron marked this pull request as ready for review August 12, 2026 09:09
@pr-assigner-resolver-posthog
pr-assigner-resolver-posthog Bot requested review from a team August 12, 2026 09:10
@pr-assigner-resolver-posthog

Copy link
Copy Markdown

👀 Auto-assigned reviewers

These soft owners were skipped because they only have minor changes here. Nothing blocks merge, so self-assign if you'd like a look:

  • @PostHog/team-workflows (products/workflows/product.yaml)
  • @PostHog/team-replay (nodejs/src/owners.yaml)

Soft owners come from each directory's owners.yaml and each product's product.yaml (resolved nearest-file-wins). The locator after each owner is the file that decided it. Generated files and lockfiles are ignored when deciding ownership.

@greptile-apps

greptile-apps Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor
Prompt To Fix All With AI
### Issue 1
.github/workflows/ci-nodejs.yml:389-391
**Unrebased branches lack DynamoDB**

When this workflow runs for an unrebased pull request, it requests the new `dynamodb` service from the branch's older Compose file, where that service is undefined, causing the Node.js job to fail before tests run.

### Issue 2
nodejs/tests/session-replay/recording-api/recording-api.integration.test.ts:52-53
**Loopback endpoints break sandbox tests**

When this test runs inside a PostHog sandbox, these addresses target the app container itself while DynamoDB and object storage are sibling Compose services without published ports, causing table setup and S3 operations to fail with connection errors.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "revert(dev): keep the localhost ses endp..." | Re-trigger Greptile

Comment thread .github/workflows/ci-nodejs.yml Resolved
@rnegron
rnegron requested review from a team, TueHaulund and fasyy612 and removed request for a team August 13, 2026 11:37
@rnegron
rnegron requested review from arnohillen and ksvat August 13, 2026 11:37

@arnohillen arnohillen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[arno's agent] Reviewed at head f6520ec: change verified against head code, CI classified, no blocking findings.

@rnegron rnegron changed the title chore(dev): replace localstack with dynamodb-local chore(dev): drop localstack from the dev stack Aug 13, 2026
@arnohillen

Copy link
Copy Markdown
Contributor

[arno's agent] The gate rename from LOCALSTACK_DISABLED to DYNAMODB_TESTS_DISABLED silently un-skips for devs who exported the old name, giving connection errors when the dynamodb profile is down.

@arnohillen

Copy link
Copy Markdown
Contributor

[arno's agent] The CMD-SHELL healthcheck's exec 3<>/dev/tcp/... only works because the image's /bin/sh is bash; a POSIX sh would fail every probe, worth a comment since the image tag pin is the only guard.

Comment thread docker-compose.dev.yml
service: localstack
container_name: '${LOCALSTACK_DOCKER_NAME:-localstack-main}'
service: dynamodb
profiles: [dynamodb]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏻

So one less container by default

@webjunkie webjunkie left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good from my side.

* To skip localstack tests (e.g. when localstack isn't running):
* LOCALSTACK_DISABLED=1 pnpm jest src/session-replay/recording-api/recording-api.integration.test.ts
* The encryption contract runs against MemoryKeyStore and always runs. The DynamoDBKeyStore
* block covers what only a real DynamoDB can show: the composite key, the conditional writes,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This says the block covers "the conditional writes", but nothing calls generateKey twice for the same session, so attribute_not_exists(session_id) and the ConditionalCheckFailedException fallback only get hit by a mocked unit test. Already true before this PR, but the comment now claims it.

Worth adding, it's the cheapest real-DynamoDB test left. The second one especially, since if that condition expression ever breaks, re-ingesting a deleted session overwrites its tombstone and un-shreds it:

it('returns the existing key instead of overwriting on a second generate', ...)
it('does not resurrect a shredded session on regenerate', ...)

Or just drop "the conditional writes" from the line.


if (command instanceof GenerateDataKeyCommand) {
const { NumberOfBytes, EncryptionContext } = command.input
const plaintext = sodium.randombytes_buf(NumberOfBytes ?? sodium.crypto_secretbox_KEYBYTES)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stub takes two things real KMS won't, so a regression would go green here and fail in prod. NumberOfBytes ?? crypto_secretbox_KEYBYTES means a missing or silly value quietly works, where KMS wants 1..1024. And the context serializer is happy with a number, so if someone drops the String(teamId) in dynamodb-keystore.ts:40 you'd get a ValidationException on every key generation in prod and nothing here.

Rejecting both would make the parity actually enforced rather than just claimed.

Comment thread devenv/news.txt Outdated
@@ -1,3 +1,6 @@
2026-08-12
localstack is gone (its license stopped being free); if you still have a localstack-main container, run hogli docker:services:down to clear it

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hogli docker:services:down has no -p, so the project name comes from the directory you're in. From a worktree it hits a different compose project and clears nothing, which is where a lot of us work. docker rm -f localstack-main works anywhere.

Comment thread docker-compose.dev.yml

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Putting dynamodb on nodejs_session_replay means every replay dev stack runs a JVM container that only the test uses. Fine if that's on purpose, just flagging it idles heavier than what it replaces.

@trunk-io
trunk-io Bot merged commit 8c2d6e3 into master Aug 18, 2026
349 of 359 checks passed
@trunk-io
trunk-io Bot deleted the worktree-migrate-off-localstack branch August 18, 2026 21:43
@deployment-status-posthog

deployment-status-posthog Bot commented Aug 18, 2026 •

Copy link
Copy Markdown

Deploy status

Environment Status Deployed At Workflow
dev ✅ Deployed 2026-08-18 22:16 UTC Run
prod-us ✅ Deployed 2026-08-18 22:44 UTC Run
prod-eu ✅ Deployed 2026-08-18 22:45 UTC Run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

chore(devex): migrate off LocalStack

4 participants