This guide gets a local copy of Project Sidewalk running on your machine. Everything runs in Docker, so you do not install Scala, Node, or Postgres directly — you only need Docker, Git, and a terminal.
New contributor? Read
CONTRIBUTING.mdfirst for the branch/PR workflow and coding standards. For a deeper tour of the architecture, seedocs/architecture.md.Stuck? Jump to Troubleshooting or Getting help.
- Docker (Docker Desktop on macOS/Windows, Docker Engine + Compose on Linux) — installed and running.
- Git and terminal/shell access.
- From a maintainer (the app will not start without these): a
docker-compose.override.ymlwith local secrets and one or more database dumps to seed Postgres. Email the lead engineer, Mikey (saugstad@cs.washington.edu), to request them. - API keys — the override file from a maintainer includes our keys. If you're outside the team, you'll need to create your own Google Maps and Mapbox keys and add them to the override file.
The dev setup is geared toward team members. If you're outside the team and want to stand up a server for a city we don't support, start with Onboarding a city — the tooling builds the streets and regions from open data and fills a schema; the server side is still on you.
Install Docker and Git for your platform, then clone the repo. The workflow after that is identical everywhere.
git clone https://github.com/ProjectSidewalk/SidewalkWebpage.git
cd SidewalkWebpage- Install Docker. Consider rootless Docker — a bit more setup now, but smoother development later.
- Install Docker Compose (bundled on Mac/Windows, separate on Linux).
- Clone the repo (above).
- Install Docker Desktop — pick Apple Chip for M-series Macs or Intel Chip for older models.
- Clone the repo (above).
We recommend and only support the WSL2 path — it runs a real Linux kernel in a lightweight VM, giving faster compiles and better Docker support than legacy WSL.
- Install Docker Desktop. When prompted, select Use WSL 2 (not Hyper-V).
- Install WSL2 with the default Ubuntu distro: open PowerShell as administrator and run
wsl --install. (Pin Ubuntu to your taskbar for easy access.) - Update WSL:
wsl --update. - In Docker Desktop → Settings → General, check Use the WSL 2 based engine. Then under Resources → WSL Integration, enable integration with your default distro and check Ubuntu.
- From inside the Ubuntu (Linux) shell — not
/mnt/c— clone the repo into your Linux home (e.g.~/projects/). Running from the Linux filesystem is required for acceptable performance.
You'll need make inside Ubuntu: sudo apt install make.
Moving files (e.g. database dumps) into the Linux VM
- File Explorer: in the left sidebar open
Linux → Ubuntu → home → <username> → SidewalkWebpageand drag files in. (Right-click the folder → "Pin to Quick access" to find it easily later.) Copied files often get a companion:Zone.Identifierfile — it's safe to delete those. - Command line: your Windows drives are mounted under
/mnt(e.g./mnt/c), so you cancp /mnt/c/path/to/dump ~/SidewalkWebpage/db/.
Starting / shutting down WSL2 + Docker
WSL and Docker use significant memory in the background. When you're not working on Project Sidewalk:
- Shut down: close anything using Docker/WSL, quit Docker Desktop from the tray, then run
wsl --shutdown. - Start back up: run
wsl -d Ubuntu(or just open your IDE in WSL), then launch Docker Desktop.
Make sure Docker is running (you'll see the whale icon in your tray; you can set Docker to start on login).
-
Add the secrets file. Place the
docker-compose.override.ymlfrom your maintainer in the repo root, then edit it for the city you want to run:SIDEWALK_CITY_ID— the city to run (e.g.seattle-wa); see the City IDs table.DATABASE_USER— that city's database user, replacing the defaultsidewalk(e.g.sidewalk_seattle).platform— leave it commented. Every host builds the web image for its own architecture as of #5069, so Apple Silicon is native without it. Uncomment it only if a native build fails, to force an emulatedlinux/amd64build.
-
Stage the database dumps. Put the dump files from your maintainer in the
db/directory and rename them to the exact names the import scripts expect:- City data dump →
<database_user>-dump(e.g.sidewalk_seattle-dump). - Users dump →
sidewalk_users-dump(same name regardless of city).
- City data dump →
-
Build and start the containers, dropping into a shell in the web container:
make dev
make devstarts thedbcontainer and then opens an interactive shell in thewebcontainer. The first build downloads images, spins up containers, and initializes (but does not yet populate) the database — expect ~5–30+ minutes depending on your connection. Success ends with aroot@<container-id>:/home#prompt. -
Import users and data from a second terminal on your host (outside the web-container shell):
make import-users replace=1 # load sidewalk_users-dump (the login schema) make import-dump db=<database_user> # load <database_user>-dump; db= defaults to "sidewalk"
Both restore from a binary dump and show a live elapsed-time clock — the users dump is ~1 GB, so it runs for a couple of minutes (the restore is parallelized to keep that short); a city dump varies with its size.
replace=1is for this first import only: a fresh DB has no accounts of its own to keep, and merging all ~6M accounts one by one takes about 20 minutes. Later imports merge (see Switching / adding another city). Read the output carefully — if it errors, don't continue; check Troubleshooting and ask. (Aschema "public" already existsnotice is the one error you can safely ignore.) For what each script does and the full set of DB lifecycle/maintenance targets, seedb/scripts/README.md. -
Start the app from inside the web-container shell opened by
make dev:npm start
npm startruns Grunt (JS/CSS concatenation + watch) in the background, thensbt ~ runfor continuous recompile. The first compile takes 5+ minutes; later ones are seconds. Usenpm run debugif you want a JVM debug port attached. It's ready when you seeListening for HTTP on .../9000. -
Open the app: http://localhost:9000 (or
127.0.0.1:9000). The first load is slow while Play applies evolutions and compiles on demand.
After a restart you don't repeat the import — just:
cd SidewalkWebpage
make dev # start db + open the web-container shell
npm start # (inside that shell) build assets + run the appThen visit http://localhost:9000. To stop everything: make docker-stop.
make dev also keeps node_modules in step with package-lock.json, reinstalling only when the two have diverged.
See npm dependencies.
Other handy targets:
| Command | What it does |
|---|---|
make docker-up |
Start all services detached (no shell). Useful for db only: the web container exits at once, its image command being jshell, which reads EOF without a TTY. |
make npm-sync |
Reinstall node_modules from package-lock.json if they've diverged. |
make ssh target=web |
Open a shell in a running container (target=web or target=db). |
Each city is a separate database. To switch:
- Put the new dump in
db/, renamed to<database_user>-dump(see the City IDs table). - If that dump is newer than your users dump, get a users dump at least as new, rename it
sidewalk_users-dump, and runmake import-users(ask a maintainer if unsure — the creation date is in the original filename). It merges: accounts you're missing are added, and the ones you have, including local test accounts your other cities point at, are kept, so the cities you already imported keep working.make import-users replace=1wipes the login schema and restores the dump from scratch instead. That also drops the parts of every city that depend on it (foreign keys, a survey column, a view), so after it, re-import every other city you have. make import-dump db=<database_user>(from the host, outside the Docker shell).- Update
DATABASE_USERandSIDEWALK_CITY_IDindocker-compose.override.ymlto match. make devagain.
To switch back and forth later: exit the Docker shell, change the two override values, and re-run make dev.
The SIDEWALK_CITY_ID and DATABASE_USER must correspond. In the repo, conf/cityparams.conf is the source of
truth; the snapshot below is a convenience copy (it may lag as new cities are added).
| City ID | Database User | City ID | Database User | |
|---|---|---|---|---|
| seattle-wa | sidewalk_seattle | taichung-tw | sidewalk_taichung | |
| columbus-oh | sidewalk_columbus | cliffside-park-nj | sidewalk_cliffside_park | |
| cdmx | sidewalk_cdmx | blackhawk-hills-il | sidewalk_blackhawk_hills | |
| spgg | sidewalk_spgg | columbia-sc | sidewalk_columbia | |
| pittsburgh-pa | sidewalk_pittsburgh | west-chester-pa | sidewalk_west_chester | |
| newberg-or | sidewalk_newberg | danville-il | sidewalk_danville | |
| washington-dc | sidewalk | detroit-mi | sidewalk_detroit | |
| chicago-il | sidewalk_chicago | hackensack-nj | sidewalk_hackensack | |
| amsterdam | sidewalk_amsterdam | clifton-nj | sidewalk_clifton | |
| la-piedad | sidewalk_la_piedad | maywood-nj | sidewalk_maywood | |
| la-piedad-old | sidewalk_la_piedad_old | madison-wi | sidewalk_madison | |
| oradell-nj | sidewalk_oradell | tainan-tw | sidewalk_tainan | |
| validation-study | sidewalk_validation | niagara-falls-ny | sidewalk_niagara_falls | |
| zurich | sidewalk_zurich | chandigarh-india | sidewalk_chandigarh | |
| zurich-infra3d | sidewalk_zurich_infra3d | rancagua-chile | sidewalk_rancagua | |
| taipei | sidewalk_taipei | vancouver-wa | sidewalk_vancouver | |
| new-taipei-tw | sidewalk_new_taipei | santiago-chile | sidewalk_santiago | |
| keelung-tw | sidewalk_keelung | tucson-az | sidewalk_tucson | |
| auckland | sidewalk_auckland | paterson-nj | sidewalk_paterson | |
| cuenca | sidewalk_cuenca | staging | sidewalk_zurich | |
| crowdstudy | sidewalk_crowdstudy | richmond-va | sidewalk_richmond | |
| burnaby | sidewalk_burnaby | fort-wayne-in | sidewalk_fort_wayne | |
| teaneck-nj | sidewalk_teaneck | virden-il | sidewalk_virden | |
| walla-walla-wa | sidewalk_walla_walla | gainesville-fl | sidewalk_gainesville | |
| st-louis-mo | sidewalk_st_louis | sao-paulo-brazil | sidewalk_sao_paulo | |
| la-ca | sidewalk_la | winterthur-infra3d | sidewalk_winterthur_infra3d | |
| mendota-il | sidewalk_mendota | waltham-ma | sidewalk_waltham | |
| knox-oh | sidewalk_knox | houston-tx | sidewalk_houston | |
| kaohsiung-tw | sidewalk_kaohsiung | newport-ky | sidewalk_newport_ky | |
| bayonne-fr | sidewalk_bayonne_fr | laurens-ia | sidewalk_laurens_ia |
Editor: use whatever you're productive in — the team uses both, and the codebase has two halves with different sweet spots:
- IntelliJ IDEA (free student license for Ultimate) has the most turnkey Scala/Play support.
- VS Code is excellent for the vanilla-JS frontend; add the Metals extension for Scala.
See docs/editor-setup.md for full setup of either (JDK, plugins/extensions, format-on-save).
Whichever you use, configure it to run scalafmt on Scala files (see CONTRIBUTING.md).
Database client: Valentina Studio (cross-platform), Postico (Mac), or pgAdmin (Windows/Mac). Connect with:
Host: localhost
Port: 5432
Database: sidewalk
User: postgres
Password: sidewalk
The dev server hot-reloads, so you rarely restart it.
- Scala / Twirl views —
sbt ~ runrecompiles on save; reload the browser once compilation finishes. - JavaScript / CSS — Grunt's
watchre-concatenates yoursrc/edits intopublic/js/*/build/automatically. Editsrc/files only; never editbuild/output, and don't rungruntby hand. If a newsrc/file isn't picked up, check that its path matches a glob inGruntfile.js. build.sbtor config changes — these aren't hot-reloaded. In the Docker shell pressCtrl+D, then runsbt clean, thennpm startagain.- Python (the standalone utilities in
scripts/) — the container has two interpreters.python3is the base image's 3.8, kept because the app shells out to it for in-band clustering;python3.13is where the offline tooling and its libraries live. Run offline scripts aspython3.13 scripts/.... Details inscripts/README.md; pins indocs/upgrading-libraries.md.
package-lock.json is committed, and it — not package.json — decides which versions actually get installed. CI
runs npm ci, which installs it exactly and refuses to run if it disagrees with package.json, so every developer
and every required check share one toolchain.
To add or change a dependency, edit package.json, run npm install inside the container, and commit the
resulting package-lock.json alongside it. Don't hand-edit the lockfile. On a rootful Docker daemon, run that
install as docker exec -u $(id -u) projectsidewalk-web … — npm rewrites the lockfile by rename, so the new file
lands owned by container root, and a tracked root-owned file makes the next host-side git checkout of it fail.
package.json's engines records the Node and npm the container and CI use. There's no .npmrc, so it's advisory:
npm prints an EBADENGINE warning and installs anyway. It's there to tell you what the supported pair is,
which matters for make test-e2e-host — the one path that installs on your host rather than in the container.
The container never uses your checkout's node_modules. The bind mount would otherwise lay a host-built copy — the
one make test-e2e-host needs — over the container's, so docker-compose.yml mounts a named volume over that path.
The volume outlives the container and so isn't refreshed by rebuilding the image; make dev and make npm-sync are
what refresh it, against a stamp covering package.json, package-lock.json and the container's node/npm versions.
To start completely clean:
make docker-stop
docker volume rm "$(basename "$PWD" | tr '[:upper:]' '[:lower:]')_node_modules" # your checkout dir, lowercased
make devThe quickest pass/fail on a Scala change is a compile. The sbt thin client hands the command to a background sbt server rather than starting its own:
make compileThe first call after a container boot starts the compile server (~30s); later calls are near-instant. build.sbt
sets -Xfatal-warnings, so a [success] is also warning-clean.
Use --jvm-client, not --client: the native client (sbtn) needs a newer glibc than the container's focal base,
so it dies on startup, though sbt --client --version still prints happily (#5268). A server belongs to one project
directory, so each worktree gets its own; sbt shutdownall stops every one of them, the running ~ run included.
An idle one stops itself after an hour (serverIdleTimeout) instead of holding ~1GB until you reboot.
That server serves one command at a time, so it will not run anything while ~ run is up in the same
checkout — run holds the task for as long as the app lives, and your command queues behind it forever with
nothing on screen. (A ~ compile is fine; a watch loop yields between runs.) So run the app and compile it from
different checkouts. tools/sbt-run.sh refuses with an explanation rather than hanging.
ScalaTest specs live under test/ — mostly functional specs for the public API, plus service and submission
specs. They boot the real app against Postgres+PostGIS, so the db container has to be up:
make test-scala
make test-scala only=controllers.api.PublicApiSpecOnly one checkout tests at a time. They share one db container and one city schema, and most specs commit rather
than roll back, so simultaneous runs overwrite each other's rows and stack two multi-GB JVMs — which is how
earlyoom comes to kill one mid-run. A second make test-scala says it's waiting, then starts when the first
finishes.
The backend-tests CI job is a required check and runs all of test/ (sbt coverage test, since #5042), so a
new spec file is picked up with nothing to enroll it in. Still run the suite locally before you trust it — and read
the CANCELED lines, not just the green ones. Specs assume their preconditions instead of failing on them, and they
cancel in both directions: most want rows your schema may not have, while NightlyJobStatusSpec and
ImageryPollOutcomeSpec cancel on a database holding more than CI's — every nightly job already recorded, say.
CI's seeded schema (#5115) is expected to cancel nothing, so a CANCELED line there means the seed stopped covering
something.
There are also Python unit tests for the scripts/ utilities (make test-python) and a jsdom Jest suite for
frontend modules (make test-js — Jest's node_modules are in the container, not on your host).
docs/testing-and-ci.md covers what each layer is for.
Compiling and linting can't see a runtime JavaScript error — a stale bundle, a missing global, a method that throws only when clicked. The browser smoke suite covers that: it loads every core page in headless Chromium and fails on any uncaught page error or console error. With your dev app running, in a second terminal:
make test-e2e # the whole suite
make test-e2e args="-g labelMap --no-deps" # one page
make test-e2e wt=<worktree-name> # a worktree's specs, from anywhereNothing to install: the runner is a container, so it behaves the same on macOS, Linux, and WSL — including Apple
Silicon, where it runs a native browser. CI runs the same suite on every PR as the e2e-smoke job. Full
details, including how to watch a test run headed, are in test/e2e/README.md.
If you keep in-progress branches in git worktrees (.claude/worktrees/<name>) — for example to review a
colleague's branch, or to run a second branch alongside your main checkout — you can bring that branch's app up on
http://localhost:9000 with one command:
make qa-worktree wt=<worktree-name>A worktree needs more setup than the main repo (its node_modules and built asset bundles aren't checked in, and
sbt's caches and config have to be pointed at the right places), so this target handles all of it: it links the main
repo's node_modules, builds that branch's JS/CSS bundles, starts a backgrounded grunt watch so later edits
rebuild automatically, frees :9000, kills any stray sbt server or hung sbt task sharing the worktree's target/
(either deadlocks ~ run on compile locks), and launches sbt ~ run against the worktree's own config
while reusing the main repo's warm sbt caches. The first request triggers the dev compile; Ctrl+C stops it and
reaps the grunt watch. To tear a session down out-of-band, run make qa-worktree-stop wt=<name> (add clean=1 to
also drop the node_modules symlink). It behaves the same on macOS, Linux, and WSL because the work runs inside the
web container.
Both targets run the worktree's own copy of tools/qa-worktree.sh when it has one (falling back to the main
checkout's), so the branch being QA'd supplies its own tooling. make itself still reads the main checkout's
Makefile, so when that checkout sits on a branch without the target, make reports No rule to make target; either
check out a branch that has it or run the script directly:
docker exec -it projectsidewalk-web bash /home/.claude/worktrees/<name>/tools/qa-worktree.sh <name>.
Every other container target checks the checkout you run it from. The container mounts the main checkout at
/home and so sees the worktrees inside it: make lint, make test-js, make compile, make test-scala,
make scalafmt, make test-python and the rest, run from a worktree, check that worktree, and wt=<name> points them
at one from anywhere. make lint opens by naming the tree it checks. Make stops with an error for a checkout the
container can't see (one outside the main checkout). This takes the worktree's own Makefile, so a branch older than
#5291 needs develop merged in first. The exceptions:
make test-e2eruns the worktree's specs against whatever app is on:9000, and warns when that's another checkout's. Start the worktree's app withmake qa-worktree wt=<name>first.make build-city-dataandmake check-imageryalways run in the main checkout, whosedb/the db container reads.- A hand-typed
docker exec … "cd /home && …"always runs in the main checkout.
The sbt server that make compile, make test-scala, or make scalafmt starts for a worktree stays up until it
idles out after an hour, or until make qa-worktree-stop wt=<name> or make worktree-remove wt=<name> stops it.
Each checkout has its own target/. What grows without bound there is packaged build output: every sbt dist/stage writes ~1GB of jars named after the current version and removes none of the older ones. Clear just
those, keeping compiled classes so the next make compile is still incremental:
make clean-dist # this checkout
make clean-dist wt=<name> # a worktree'sWhen you're done with a worktree for good, remove it with:
make worktree-remove wt=<worktree-name>That stops its QA session, deletes the directory and the registration git keeps for it, and deletes its branch once
that branch is fully merged into develop — it tells you what it kept otherwise. Deleting the directory yourself
leaves the registration behind, so the worktree lingers in git worktree list; run the target and it cleans that up
instead. It stops and tells you if the worktree still has uncommitted or untracked files (add force=1 to delete
those along with it) or if the worktree is locked: an active Claude Code worktree session holds a lock, and git
refuses a locked worktree even with --force. Unlike the QA targets it runs host-side, because a worktree's .git
file points at the main repo by absolute host path; the direct invocation is
bash .claude/worktrees/<name>/tools/worktree-remove.sh <name>.
To QA admin-only pages you need an account with a role. The dev database is seeded from a dump that includes real
accounts, so if your own account is in it you can sign in normally — password checks work the same locally as in
production. Otherwise — or if you'd rather use a throwaway account — create a fresh one through the sign-up form and
grant it a role directly in the dev database (roles are checked per request, so you don't need to sign in again). Open a
psql shell (docker exec -it projectsidewalk-db psql -U sidewalk -d sidewalk) and run:
UPDATE sidewalk_login.user_role
SET role = 'Owner'
WHERE user_id = (SELECT user_id FROM sidewalk_login.sidewalk_user WHERE username = '<your-username>');Most routes need a session. Grab an anonymous cookie once, then reuse the jar:
curl -s -c /tmp/sidewalk_cookies.txt "http://localhost:9000/anonSignUp?url=%2F"
curl -s -b /tmp/sidewalk_cookies.txt "http://localhost:9000/v3/api/labelTypes"Each city lives in its own schema (sidewalk_<city>); authentication lives in sidewalk_login. For ad-hoc
queries, prefer the read-only role so you can't accidentally write:
docker exec projectsidewalk-db psql -U readonly_user -d sidewalk -c "\dt sidewalk_seattle.*"The schemas are essentially identical, so sidewalk_seattle is a safe default for schema questions. For anything
about data or migration state, first find out which city is actually running — don't assume:
docker exec projectsidewalk-web bash -lc 'echo $DATABASE_USER' # this value IS the active schema nameDATABASE_USER selects the schema and is authoritative; SIDEWALK_CITY_ID only selects cityparams.conf entries
(map center, bounds, display name). The two are supposed to correspond (see City IDs), but a container
can be left with them mismatched, in which case the app renders one city's params over another city's data: an empty
map with no error in any log. Confirm what the app believes it is with
curl -s -b <cookie-jar> localhost:9000/labelmap | grep -oE 'cityId: "[^"]*"'.
Two more things to know before drawing conclusions from a query:
-
readonly_usercannot see every schema, and the failure is silent. It is granted per-schema, so it may have no rights on the active city's schema, andinformation_schema/\dtsimply omit what you can't see rather than erroring, which reads as "that schema doesn't exist" or "that evolution never applied".pg_namespaceis world-readable, so enumerate with it, then query the city as its own role:docker exec projectsidewalk-db psql -U readonly_user -d sidewalk -tAc \ "SELECT nspname FROM pg_namespace WHERE nspname LIKE 'sidewalk%' ORDER BY 1" docker exec projectsidewalk-db psql -U sidewalk_teaneck -d sidewalk -c \ "SELECT max(id) FROM sidewalk_teaneck.play_evolutions"
-
The dev DB is not representative of production size, and some tables may be absent. The two largest production tables by a wide margin are
audit_task_interactionandvalidation_task_interaction(raw per-action interaction logs). The dev dumps omit them to stay manageable, so locally they are typically empty or missing. Never infer a table's production size or existence from the local DB; when reasoning about query cost or indexes, treat those two, notwebpage_activity, as the heavyweight logs.
Roughly ordered by when you'd hit them during setup.
| Symptom | Fix |
|---|---|
make: docker-compose: No such file or directory |
Newer Docker uses docker compose (no hyphen). Edit the Makefile to replace docker-compose with docker compose. |
Docker-Compose command fails on Mac |
Recreate the symlink per the Compose install docs. |
gpg: keyserver receive failed during build (Windows) |
Add ENV APT_KEY_DONT_WARN_ON_DANGEROUS_USAGE=1 near the top of the Dockerfile. |
pg_restore: ... schema "public" already exists |
Safe to ignore — no effect. |
import-dump otherwise errors |
Don't skip ahead. Re-check the dump filename and db= value, then see the Troubleshooting wiki and ask. |
Execution exception [NoSuchElementException: None.get] at runtime |
The data wasn't imported — run make import-dump (the init only creates the schema, not the data). |
Database suddenly looks empty (role "sidewalk_<city>" does not exist, no city schemas) |
Your data is most likely parked on an orphaned Docker volume, not gone — docker volume ls -qf dangling=true lists the candidates, and you can copy one back onto this project's data volume (<project>_pgdata, where <project> is your checkout directory lowercased). Don't run docker volume prune while you're looking; that is what actually destroys them. |
Cannot create container for service web: Conflict ... name "/projectsidewalk-web" already in use |
A prior web container wasn't shut down cleanly: docker container rm /projectsidewalk-web. |
| Errors after the computer was shut off mid-run (WSL) | Run wsl --shutdown; when Docker offers to restart WSL, accept. Otherwise restart Docker manually. |
| Can't connect to the database | The db container may not be listening on all addresses. make ssh target=db, edit /var/lib/postgresql/data/postgresql.conf, set listen_addresses = '*'. |
make commands "just don't work" |
Reinstall make. As a fallback, run the underlying command from the Makefile directly (e.g. make ssh target=web ≈ docker exec -it projectsidewalk-web /bin/bash). |
relation "role" does not exist while a schema is applying evolutions |
That schema is behind evolution 372, which dropped the shared sidewalk_login.role lookup table that evolutions 270, 295, 337 and 355 all read. Recoverable with the data intact: Recovering a schema stranded below evolution 372. |
A new src/ JS file isn't bundled |
Make sure its path matches a glob in Gruntfile.js. |
| First compile seems stuck | It isn't — initial dependency resolution is genuinely slow. Watch the container logs. |
| Compiles are slow on Apple Silicon | Your projectsidewalk/web image may predate #5069 and still be x86_64 — Compose reuses a locally tagged image instead of rebuilding it, so pulling that change alone doesn't help. Check with docker image inspect projectsidewalk/web --format '{{.Architecture}}' (expect arm64); if it says amd64, rebuild with make docker-stop && docker compose build web. Also make sure platform is commented out in your docker-compose.override.yml. |
Evolution 372 turned the shared sidewalk_login.role lookup table into an enum of the same name. Evolutions 270,
295, 337 and 355 read that table, so once 372 has run for any one city, a schema below 356 can't replay them.
Re-importing does not help: every committed city dump is at evolution 335 or below, so it lands in the same
hole. Only sidewalk_init-dump, the empty template, is past 372. Editing the old evolution files isn't the fix
either — Play would revert every schema that already applied them.
What does work is giving the old evolutions the table they expect for the length of the replay. The conflict is
only over the name: Postgres won't hold a table and an enum called role in one schema at once.
- Move
372.sqland373.sqlout ofconf/evolutions/default/, so no city drops the table mid-replay. - Park the enum and rebuild the lookup table. Evolution 295 also writes
user_role.role_id, which 372 renamed and retyped, and 355 joins on it, so it needs a populated shim — not just a placeholder:ALTER TYPE sidewalk_login.role RENAME TO role_parked; CREATE TABLE sidewalk_login.role (role_id SERIAL PRIMARY KEY, role TEXT NOT NULL); ALTER TABLE sidewalk_login.role OWNER TO sidewalk; INSERT INTO sidewalk_login.role (role_id, role) VALUES (1, 'Registered'), (2, 'Turker'), (3, 'Researcher'), (4, 'Administrator'), (5, 'Owner'), (6, 'Anonymous'), (7, 'AI'); SELECT setval('sidewalk_login.role_role_id_seq', 7); ALTER TABLE sidewalk_login.user_role ADD COLUMN role_id INT; UPDATE sidewalk_login.user_role SET role_id = CASE role::TEXT WHEN 'Registered' THEN 1 WHEN 'Turker' THEN 2 WHEN 'Researcher' THEN 3 WHEN 'Administrator' THEN 4 WHEN 'Owner' THEN 5 WHEN 'Anonymous' THEN 6 WHEN 'AI' THEN 7 END;
- Boot the app once per stranded schema, which takes each to 371. Evolutions apply on the first request, so
docker exec -e DATABASE_USER=<schema> -e SIDEWALK_CITY_ID=<city-id> projectsidewalk-webasbt "run 9000"andcurlit, one schema at a time. - Undo step 2, in reverse.
CASCADEclears thesurvey_questionFKs that 337 re-added; 372 drops them itself on the way past:DROP TABLE sidewalk_login.role CASCADE; ALTER TABLE sidewalk_login.user_role DROP COLUMN role_id; ALTER TYPE sidewalk_login.role_parked RENAME TO role;
- Put
372.sqland373.sqlback and boot each schema once more. 372's shared half self-skips on its enum guard, so only its per-city half runs, then 373.
Schemas already past 372 are never touched, and need no down-migration.
Two other evolutions can strand a schema the same way, because they read shared state a later evolution removed:
column "infra3d_access" does not exist— 313 reads it and 316 drops it. A schema below 303 self-heals, since 303 re-adds the column withIF NOT EXISTS; one in the 303–312 window needs it restored (exactly as 316's own Downs does) before each run, because 316 drops it again on the way past.function replace(jsonb, unknown, unknown) does not exist— 276 convertsconfig.excluded_tagsfrom text to jsonb. A dump whoseplay_evolutionspredates 276 but whoseconfigwas already converted can't replay it; restore the column to the pre-276 text value from that schema's own dump.
Both are symptoms of the same underlying problem: a committed dump whose contents disagree with the
play_evolutions it ships with. If an evolution fails on a table or column that should exist at that level, check
the dump (pg_restore -s -t <table> -f - db/<dump>) before assuming your local DB drifted.
Slick query errors while developing:
value transactionally is not a member of slick.dbio.DBIOAction...→ addimport models.utils.MyPostgresProfile.api._.type mismatch ... NoStream,Nothing ...(often misleading) → try wrapping the queries in.transactionally, or useDBIO.seq().andThen().
- Team members: ask in the #core or #interns Slack channels (we prefer channels over DMs so everyone can learn and help).
- Anyone: search the issues tagged "Dev Environment", check the Troubleshooting wiki, or email sidewalk@cs.uw.edu.
If you solve something not covered here, please add it.