Local WSL operations¶
Run project commands inside Ubuntu WSL from:
Docker Desktop supplies the Linux Docker Engine through WSL integration. Node, npm, Git, and the worktree remain Linux-native. AndreaWeb does not modify shell, SSH, GitHub CLI, or Azure CLI configuration.
Local topology¶
| Service | Local address | Persistent state |
|---|---|---|
| Landing and architecture through Nginx | http://localhost:8080/ |
none |
| Medusa DTC storefront through Nginx | http://localhost:8080/guanza |
Medusa/PostgreSQL |
| Direct Medusa DTC storefront | http://localhost:3100/guanza |
Medusa/PostgreSQL |
| Medusa API and Admin | http://localhost:9000 |
PostgreSQL |
| Medusa Admin development assets | http://localhost:5173 |
none |
| PostgreSQL | private Docker network only | postgres_data volume |
| Redis | private Docker network only | redis_data volume |
| Garage S3 API | private Docker network only | Garage volumes |
| Garage public media | http://*.web.garage.localhost:3902 |
garage_meta and garage_data volumes |
The local Nginx container rehearses the reverse-proxy boundary. Production uses the VPS's existing system Nginx instead of this container.
Prerequisites¶
- Docker Desktop with WSL integration for
Ubuntu-26.04; - Docker Compose v2;
- Node.js 22.13 or newer through the WSL
fnminstallation; - OpenSSL and curl in WSL;
- the repository checked out on the WSL filesystem.
Verify the boundary before troubleshooting the application:
node and npm should resolve under /home/justin, not /mnt/c. The
dev-env wrappers for ssh, gh, and az intentionally delegate to their
authenticated Windows counterparts; private keys and credential stores are not
copied into WSL.
First complete startup¶
Install dependencies and create the ignored, mode-0600 local environment:
The environment generator creates random local database, application, and Garage credentials. It never prints their values. Running it again preserves existing values and adds any newly required fields.
Build the images, prepare the database, and start the complete stack:
docker compose --profile commerce build medusa
docker compose --profile commerce run --rm medusa npm run db:migrate
npm run storefront:env
docker compose --profile commerce up -d --build
npm run media:import:starter
npm run admin:invite:local -- andrea@andreazambrano.co
npm run commerce:verify
npm run health:local
The migration command is intentionally explicit. It applies schema migrations
and one-time seed migrations, including the Colombia/COP foundation and the four
starter products. storefront:env reads the seed-created publishable key from the
local database into ignored environment files without printing it. The media
import uploads the existing product images through Medusa's S3 provider and
safely skips products that already have images.
Open:
http://localhost:8080/http://localhost:8080/arquitecturahttp://localhost:8080/guanza(the real Medusa DTC storefront)http://localhost:8080/en/arquitecturahttp://localhost:8080/en/guanzahttp://localhost:3100/guanza(direct storefront access)http://localhost:9000/apphttp://localhost:9000/health
garage-init exiting with status zero is expected. It is a one-time helper that
ensures the media bucket has public website delivery enabled.
Manual cart inquiries¶
The Medusa cart offers three non-payment actions: a WhatsApp handoff, a pre-populated email to the public store alias, and an explicit copy-to-clipboard button. Opening either external application does not clear the cart, create an order, reserve inventory, or claim that the customer sent the draft.
Configure the destinations in the ignored root .env file:
# International digits only; keep blank to use recipient-free WhatsApp sharing.
STORE_INQUIRY_WHATSAPP_NUMBER=
STORE_INQUIRY_EMAIL=cementos@andreazambrano.co
Run npm run local:env to add missing keys without replacing existing values,
then run npm run storefront:env and recreate the storefront container after a
change. The native Next.js development environment receives the same values in
its ignored, mode-0600 .env.local file.
The WhatsApp number is server-only. The cart submits a same-origin POST to the
storefront BFF, which reloads the Medusa cart and returns an uncacheable 303
redirect containing the localized draft. The number is absent from the HTML,
JavaScript bundle, repository, and JSON responses, although a determined visitor
can still inspect the final redirect. Use a dedicated WhatsApp Business number
before treating the destination as a public customer-service channel.
The form targets a new tab so the populated cart stays open. The BFF rejects
cross-site submissions, empty carts, and direct GET requests; Nginx forwards
the original host and port so the same-origin check works through the local
:8080 rehearsal proxy.
Portfolio-only preview¶
The landing and architecture application can run without the commerce profile:
That starts only the portfolio application and disposable local Nginx proxy.
The /guanza route intentionally returns an upstream error in this mode
instead of falling back to the retired hard-coded catalog; start the commerce
profile whenever the storefront is needed.
Local application preview¶
Normal Compose serves the landing and architecture pages from the portfolio
app and /guanza plus /en/guanza from the Medusa storefront. The first
redesign's route-spike overlay is retired. From the WSL repository:
The local storefront builds before starting so its production-like asset paths
work behind the split-app proxy, without the previously observed development
HMR/hydration failure. This source-mounted container needs a rebuild/restart
after code edits; it is not hot-reloading. For direct native Next development,
run npm run storefront:dev after stopping the container occupying port 3100;
its .env.local deliberately leaves the asset prefix empty for direct access.
If the persistent
storefront_node_modules development volume is missing new packages after a
lockfile change, update only that volume and restart the storefront; do not
delete the database or media volumes:
docker compose --profile commerce exec -T -u root storefront npm ci
docker compose --profile commerce exec -T -u root storefront \
chown -R node:node /storefront/node_modules
docker compose --profile commerce restart storefront
For an empty local catalog after the foundation seed, prepare the two collections and assign the existing starter products. To review Piezas Intervenidas as well, run the existing opt-in sample import scripts. Do not run these commands against the VPS or a production database:
docker compose --profile commerce exec -T medusa \
npx medusa exec ./src/scripts/setup-local-collection-fixtures.ts
docker compose --profile commerce exec -T medusa \
npx medusa exec ./src/scripts/seed-sample-piezas.ts
docker compose --profile commerce exec -T medusa \
npx medusa exec ./src/scripts/import-sample-piezas-media.ts
Open http://localhost:8080/, /arquitectura, /guanza, and their /en
counterparts. Set ARCHITECTURE_WHATSAPP_NUMBER and
STORE_INQUIRY_WHATSAPP_NUMBER in the ignored local .env only if testing the
contact redirects; both are server-side runtime settings. Recreate the
affected frontend container to pick up an environment change. Inspect a
same-origin POST's Location header without following it or sending a message.
Account creation is intentionally absent from the new header pending the
account readiness audit.
The optional media adapter is disabled by default; see ngrok sharing to enable it for a remote laptop review. Local Nginx preserves the forwarded HTTPS scheme and the original host/port for contact origin validation. Neither feature changes production configuration.
Production-image rehearsal¶
Use the reusable release-review overlay to build isolated frontend images against local Medusa and serve them on loopback port 8081. It does not replace the normal frontends or modify production data. First synchronize the local environment and ensure the local commerce services are running:
npm run storefront:env
docker compose -f compose.yaml -f compose.release-review.yaml \
--profile commerce --profile release-review up -d --build \
release-web release-storefront release-nginx
node scripts/check-release.mjs
The rehearsal has no media URL rewrite; its local Garage URLs are reachable on this computer, not on another laptop. For remote review use port 8080 with the explicit media settings below. Stop only the rehearsal containers when finished:
docker compose -f compose.yaml -f compose.release-review.yaml \
--profile release-review stop release-nginx release-storefront release-web
node scripts/check-release.mjs --local checks the normal port 8080 entry point.
--production selects read-only checks of the fixed public HTTPS site.
The optional check-app-layout.mjs uses separately installed Playwright tooling
for the local viewport matrix; --release-review selects port 8081. Its product
fixtures must exist locally. Do not seed production to make browser tests pass.
Create a local Medusa administrator¶
Create an invitation for the intended local administrator:
The command uses Medusa's supported user-invitation flow instead of placing a
password on a command line. It writes the local acceptance URL to the ignored
mode-0600 file work/local-admin-invite.txt. Open that URL, let Andrea choose
her development password, confirm that she can sign in at
http://localhost:9000/app, and then delete the invitation file.
Rerunning the command before acceptance replaces the earlier pending invitation
and invalidates its URL. When Medusa RBAC is enabled, the workflow explicitly
assigns the built-in super-admin role; otherwise Medusa's standard superuser
semantics apply.
The invitation is local database state and survives ordinary container rebuilds. Creating the eventual production Andrea account remains a separate, deliberate deployment action with a new invitation and password.
Once signed in, follow the
Medusa Admin product guide to create products,
upload and assign images, configure variants and COP prices, and connect stock
to Inventario principal.
Verify all Step 7 foundation records and their important links:
This fails unless the store defaults to COP, the Colombia region includes CO, the Cementos sales channel is linked to the primary stock location and publishable key, all four seed products are published with positive COP prices and managed inventory, and either a development Admin user or pending invitation exists. It does not expose the publishable token or Admin invitation token.
Routine validation and diagnostics¶
npm run health:local
npm run commerce:verify
npm run lint
npm test
npm run build
docker compose --profile commerce config --quiet
The health command checks all running containers, their configured Docker health checks, both storefront previews, Medusa API/Admin, and the Garage bucket. It is read-only.
Exercise an actual Medusa upload, public read, Garage restart, second read, and cleanup:
This creates only a temporary verification object and removes it on success or failure. The four catalog images remain untouched.
Useful diagnostics:
docker compose --profile commerce ps -a
docker compose logs --follow --tail=100 medusa
docker compose logs --follow --tail=100 storefront
docker compose logs --follow --tail=100 postgres redis garage
docker stats
docker system df
PostgreSQL, Redis, the Garage S3 API, and the Garage admin API are not published
to the LAN or internet. The development Medusa and Garage website ports bind to
127.0.0.1 only.
Restarting and stopping safely¶
Restart replaceable processes while preserving data:
Stop and remove project containers and networks while retaining named volumes:
Start again with:
The development backend and storefront mount named node_modules volumes over
the dependency directories baked into their development images. On first use,
Docker copies the image's installed packages into each empty volume. This keeps
Linux dependencies off the source bind mount and makes routine container
restarts fast. Docker does not refresh an existing volume merely because an
image was rebuilt, however. After either application's lockfile changes, remove
only that application's replaceable dependency/cache volumes and recreate it.
For the storefront:
docker compose stop storefront
docker compose rm -f storefront
docker volume rm andreaweb_storefront_node_modules \
andreaweb_storefront_next_cache
docker compose --profile commerce up -d --build storefront
Those two storefront volumes contain downloaded packages and generated Next.js
output only; rebuilding recreates them. They are intentionally different from
postgres_data, redis_data, garage_meta, and garage_data, which hold
application state and must not be removed during routine work.
Production will not use dependency or framework-cache volumes. Its immutable, multi-stage images will contain the exact dependencies and compiled output from the reviewed lockfile. Only stateful services—PostgreSQL, persistent Redis when enabled, and Garage media/metadata—will use durable volumes on the VPS.
Do not add --volumes or -v to docker compose down during routine work.
That explicitly deletes the PostgreSQL, Redis, and Garage volumes and therefore
destroys local products, inventory, users, and uploaded media.
npm run local:env:rotate rotates the local database password, JWT secret, and
cookie secret while preserving existing Garage credentials. Use it deliberately;
containers must be recreated afterward so they receive the new values.
Sharing with ngrok¶
Authenticate the native WSL ngrok agent once; never commit or paste its token into repository files.
With the local application preview running,
share the complete landing, architecture, and Guanza catalog through the
single local Nginx entry point. For remote product images, explicitly enable
these settings in the ignored root .env and recreate the storefront:
NEXT_PUBLIC_MEDIA_SOURCE_ORIGIN=http://andreaweb-media.web.garage.localhost:3902
NEXT_PUBLIC_MEDIA_PROXY_PATH=/guanza/media
Medusa already has the files in Garage. The adapter rewrites only the matching local media origin into a same-host path, which local Nginx forwards to the public Garage bucket. It does not alter stored media URLs or upload anything. Keep these settings out of production. Remove or blank them and recreate the storefront when the remote review is over. Then start the tunnel:
Open the assigned HTTPS URL yourself and check a collection image and the
architecture WhatsApp handoff before sending it to Andrea. The URL is public
to anyone who has it; the tunnel, Docker stack, and computer must stay running
while she reviews. Stop the tunnel with Ctrl+C; the Docker stack keeps
running. Do not treat this temporary URL as the production release or as
approval to change VPS routes.
The Medusa DTC storefront can be tunneled separately:
Do not use a direct 3100 tunnel for the complete site review. It bypasses
the preview-only media route and split-app navigation; product-image URLs
will still point at local-only services. The VPS deployment will use the
public api and media hostnames instead of this temporary proxy.
The Admin can technically be tunneled from port 9000, but do not share it
until an administrator exists and the exposure is deliberate. Local Garage
media URLs remain local-only without the optional media rewrite. A production
release uses the real HTTPS admin, api, and media hostnames on the VPS,
not the temporary preview proxy.
An optional ngrok container can share the PoC without installing the native agent:
read -rsp "ngrok authtoken: " NGROK_AUTHTOKEN
echo
export NGROK_AUTHTOKEN
docker run --rm -it \
--name andreaweb-ngrok \
--network andreaweb_default \
-e NGROK_AUTHTOKEN \
ngrok/ngrok:latest \
http nginx:80
unset NGROK_AUTHTOKEN
Common problems¶
A required environment variable is missing¶
The first command upgrades an older ignored .env without replacing existing
credentials.
The storefront key is missing or Store API requests return 401¶
Medusa creates the publishable storefront key during the database seed, so a clean installation cannot configure the DTC app before migrations have run. Use this order:
docker compose --profile commerce run --rm medusa npm run db:migrate
npm run storefront:env
docker compose --profile commerce up -d --build --force-recreate medusa storefront
The synchronization command reads the active publishable key from PostgreSQL,
updates only ignored local environment files, and adds port 3100 to the local
Medusa CORS lists. It never prints the key. Recreating Medusa is necessary when
an already-running container still has the older CORS environment.
A native storefront build reports .next permission or missing-page errors¶
An older root-running storefront container could create root-owned files in the
WSL worktree. A running dev compiler could also race a simultaneous native
next build when both used apps/storefront/.next.
The current setup prevents both problems: the container runs as UID/GID 1000
(node) and mounts its own storefront_next_cache volume at
/storefront/.next. Native WSL builds continue to use the worktree's .next
directory, so the two compilers no longer share build output.
For a checkout that was started with the older configuration, repair ownership once before rebuilding:
docker compose stop storefront
sudo chown -R "$(id -u):$(id -g)" apps/storefront/.next
docker compose run --rm --no-deps -u root storefront \
chown -R node:node /storefront/.next
docker compose --profile commerce up -d --build storefront
These commands change only disposable Next.js build-cache ownership. They do not touch PostgreSQL, Redis, Garage, product media, or other persistent data.
Changing the storefront base path¶
The supported basePath is empty. /guanza and /en/guanza are application-owned
routes, not a Next basePath. Docker uses /guanza as the built asset prefix and
Nginx rewrites those asset requests to Next's internal /_next path. Run
npm run storefront:env to replace an old local /cementos setting, then
recreate/rebuild the storefront. Changing Nginx alone cannot change a compiled
basePath. Native direct development uses an empty asset prefix instead.
Storefront production dependency audit¶
The controlled Next.js 16.3.6 and React 19.2.8 upgrade is complete. Both
npm --prefix apps/storefront audit and the production-only audit report zero
known vulnerabilities. Continue upgrading deliberately and do not use
npm audit fix --force to cross framework major versions.
Next.js regenerates apps/storefront/next-env.d.ts for the active development
or production output directory. It is intentionally ignored, as recommended by
current Next.js documentation, so running next dev, next build, or
next typegen does not create a false source change.
The four initial catalog products currently have zero seeded inventory. Product and cart routes can be validated, but Add to cart correctly remains disabled until inventory is assigned through Medusa Admin or a deliberate test fixture. That stock state is catalog data, not a Next.js upgrade failure.
A service is unhealthy¶
npm run health:local
docker compose --profile commerce ps -a
docker compose logs --tail=150 SERVICE_NAME
Inspect the failing service before restarting or removing anything.
Nginx returns 502 during startup¶
docker compose logs --tail=100 web nginx
curl --retry 10 --retry-delay 1 --retry-connrefused \
http://localhost:8080/portfolio
Confirm Docker is using the WSL checkout¶
docker inspect andreaweb-web-1 \
--format '{{ index .Config.Labels "com.docker.compose.project.working_dir" }}'
Expected output: