Local Dev Setup
This guide walks through the complete setup of the GXP local development environment from a fresh clone to a fully running application. The stack uses Docker Compose to orchestrate a PHP 8.3 Laravel Octane/RoadRunner app server, Horizon queue workers, Reverb WebSocket server, a Node.js Vite dev server, Nginx with wildcard SSL for local .gxp.test domains, PostgreSQL 17, and Valkey (Redis-compatible) — with optional services for WebRTC (Mediasoup + Coturn), browser testing (Selenium), and a documentation site.
Quick Start
# Clone the repo
git clone git@github.com:GramercyTech/experience-portal.git
cd experience-portal
# Run the interactive setup
./setup.sh
The setup script handles everything: prerequisites check, module selection, SSL certificates, nginx config generation, DNS, Docker build, dependency installation, and initial database seeding. It is safe to re-run at any time.
Prerequisites
| Tool | Required | Notes |
|---|---|---|
| Docker Desktop | Yes | Includes Docker Compose. Allocate 12 GB+ RAM (8 GB minimum), 2+ CPUs. |
| mkcert | Yes | brew install mkcert — local SSL certificate generation |
| Git | Yes | For submodules and hooks |
| Node.js | Only for host-mode Vite | See .nvmrc for version |
| dnsmasq | Recommended | brew install dnsmasq — wildcard DNS for *.gxp.test |
Repository access: SSH key or HTTPS credentials configured for the GXP GitHub org.
What setup.sh Does
The interactive script walks you through each step:
- Prerequisites check — verifies Docker, Docker Compose, mkcert, and Git
- Module configuration — choose which optional services to enable (creates
modules.env) - Environment file — generates
.envfrom.env.example, setsCOMPOSE_PROFILES - Docker Compose — copies
docker-compose.yaml.exampletodocker-compose.yaml - SSL certificates — generates
*.gxp.testwildcard certs via mkcert - Nginx config — generates
nginx.local.conffrom templates, deploys site configs tosites-enabled/ - DNS — offers dnsmasq (recommended) or
/etc/hostssetup - Git submodule — initializes the
documentation/submodule - Git hooks — configures
core.hooksPathtogithooks/ - Build & start — builds Docker images, starts containers, installs dependencies, runs migrations and seeders
Module System
GXP uses a modular approach to local development. Optional services are controlled by modules.env:
VITE_MODE=docker # docker | host
MODULE_WEBRTC=false # mediasoup + coturn
MODULE_DBTOOLS=false # expose database port for external tools
MODULE_DOCS=false # documentation site at docs.gxp.test
MODULE_TESTING=false # selenium browser testing
Run ./setup.sh --configure-only or bin/gxp configure to reconfigure modules. This regenerates nginx configs and updates COMPOSE_PROFILES in .env.
What Each Module Controls
| Module | Docker Services | Nginx Configs | Domains / Ports |
|---|---|---|---|
| Core (always) | app, workers, reverb, nginx, database, redis | gxp.test.conf | gxp.test, dashboard.gxp.test, api.gxp.test, assets.gxp.test |
VITE_MODE=docker | vite | (included in gxp.test.conf) | — |
MODULE_WEBRTC | mediasoup, coturn | mediasoup.conf, turn.conf | medias.gxp.test, turn.gxp.test |
MODULE_DBTOOLS | database-expose (socat proxy) | — | localhost:54320 |
MODULE_DOCS | docs | docs.conf | docs.gxp.test |
MODULE_TESTING | selenium | — | — |
The bin/gxp Helper CLI
All common operations are available through the helper CLI instead of raw docker compose exec commands:
Container Lifecycle
bin/gxp up # Start containers (+ host services if configured)
bin/gxp down # Stop containers (+ host services if configured)
bin/gxp restart [service] # Restart all or specific service
bin/gxp logs [service] # Tail logs (default: app)
bin/gxp status # Show running services + active worktree
When DATABASE_MODE, REDIS_MODE, or VITE_MODE is set to host in modules.env, up will automatically start those host services before launching containers, and down will stop them after containers are stopped. restart (with no arguments) cycles both containers and host services.
Development Commands
bin/gxp artisan migrate # Run artisan commands
bin/gxp artisan tinker # Open Tinker REPL
bin/gxp composer require foo # Run composer commands
bin/gxp npm install # Run npm commands
bin/gxp install # composer install + npm install
bin/gxp clean-install # Delete vendor + node_modules and reinstall from scratch
bin/gxp update # composer update + npm update
bin/gxp shell [service] # Interactive shell (default: app)
Database Management
bin/gxp db:backup my-save # Backup to backups/my-save-<timestamp>.sql
bin/gxp db:restore <file> # Restore from backup
bin/gxp db:list # List available backups
bin/gxp db:fresh # migrate:fresh --seed (with confirmation)
bin/gxp db:reset-admin # Reset admin email/password
bin/gxp db:unlock-admins # Unlock all locked admin accounts
Cache & Rate Limits
bin/gxp flush:rate-limits # Clear all rate limiters (stored in Redis cache)
bin/gxp flush:all # Clear cache, sessions, rate limits, and unlock all admins
Host Services
When DATABASE_MODE, REDIS_MODE, or VITE_MODE is set to host, these services run on your machine instead of in Docker. bin/gxp up and down handle them automatically, but you can also manage them directly:
bin/gxp services:start # Start all host services (Postgres, Valkey, Vite)
bin/gxp services:stop # Stop all host services
bin/gxp services:restart # Restart all host services
bin/gxp services:status # Show status of host services
Vite Dev Server (host mode)
bin/gxp vite:start # Start Vite dev server in background
bin/gxp vite:stop # Stop background Vite dev server
bin/gxp vite:restart # Restart Vite dev server
bin/gxp vite:logs # Tail Vite dev server logs
Documentation
bin/gxp docs:start # Start documentation site (Docker or host)
bin/gxp docs:stop # Stop documentation site
bin/gxp docs:restart # Restart documentation site
bin/gxp docs:logs # Tail documentation site logs
bin/gxp docs:update # Pull latest documentation submodule
Setup & Diagnostics
bin/gxp setup # Run the interactive setup script
bin/gxp configure # Re-run module configuration only
bin/gxp doctor # Diagnose issues, fix services, resolve port conflicts
bin/gxp uninstall # Remove all local GXP configuration and containers
bin/gxp help # List all commands
bin/gxp version # Show GXP version
Worktree Switching
bin/gxp switch feature-xyz # Switch to .worktrees/feature-xyz
bin/gxp switch /path/to/tree # Switch to absolute path
bin/gxp switch . # Switch back to main project root
Vite: Docker vs Host Mode
Docker Mode (default)
Vite runs in a container. No local Node.js required. HMR works via nginx proxy (assets.gxp.test:443 -> vite:5173).
Host Mode
Vite runs on your host machine for faster HMR and rebuild times. Requires local Node.js.
# In modules.env, set:
VITE_MODE=host
# Re-run configuration:
./setup.sh --configure-only
# Start vite on your machine:
npm install
npm run dev
Nginx proxies to host.docker.internal:5173 so the rest of the stack works the same.
DNS Configuration
Option 1: dnsmasq (Recommended)
Wildcard support means any *.gxp.test subdomain resolves automatically — no editing files when adding new services.
brew install dnsmasq
# Add wildcard rule
echo "address=/.gxp.test/127.0.0.1" >> $(brew --prefix)/etc/dnsmasq.conf
# Restart and create resolver
sudo brew services restart dnsmasq
sudo mkdir -p /etc/resolver
echo "nameserver 127.0.0.1" | sudo tee /etc/resolver/gxp.test
Option 2: /etc/hosts
Manual entries for each domain:
127.0.0.1 gxp.test
127.0.0.1 dashboard.gxp.test
127.0.0.1 api.gxp.test
127.0.0.1 assets.gxp.test
127.0.0.1 webhook.gxp.test
# If WebRTC enabled:
127.0.0.1 medias.gxp.test
127.0.0.1 turn.gxp.test
# If Docs enabled:
127.0.0.1 docs.gxp.test
Git Worktrees
Worktrees let you work on multiple branches simultaneously without stashing. GXP supports this with a variable mount path in Docker.
Creating a Worktree
# Create a worktree for a feature branch
git worktree add .worktrees/my-feature origin/my-feature
# Switch Docker to use it
bin/gxp switch my-feature
How It Works
- Docker containers always run from the main project root
GXP_PROJECT_PATHin.envcontrols which directory is mounted as/app- The database (named volume
gxp-pgdata) persists across switches bin/gxp switchhandles: stop -> update path -> start -> install deps -> migrate
Switching Back
bin/gxp switch . # or
bin/gxp switch main
Running setup.sh from a Worktree
If you run ./setup.sh from inside a .worktrees/<name> directory, it detects the worktree and:
- Skips Docker build (uses the main project's images)
- Skips DNS/cert setup (already done)
- Switches Docker to point at the worktree
Database
PostgreSQL data is stored in a named Docker volume (gxp-pgdata) that persists across container restarts and worktree switches.
Seeding
Seeding behavior can be configured via .env:
SEED_PORTAL_SUBDOMAIN=demo # Default portal subdomain
SEED_ADMIN_EMAIL=admin@example.com # Admin account email
SEED_ADMIN_PASSWORD=password # Admin password
SEED_DEMO_DATA=true # Include demo events/attendees
SEED_SKIP_PLUGINS=false # Skip plugin seeding
What Gets Seeded
Running db:seed (or db:fresh) creates a complete local environment:
| What | Details |
|---|---|
| Teams | 10 teams including "Gramercy Tech" as the primary team |
| Admin Account | Uses SEED_ADMIN_EMAIL / SEED_ADMIN_PASSWORD from .env. Super admin with admin + developer roles on the primary team. |
| Other Admins | 10 additional admin accounts attached to all teams |
| Permissions & Roles | Full permission set + default roles (billing, admin, member, viewer, developer) per team |
| Primary Project | "Primary Project" on Gramercy Tech team with default dashboard and welcome tile |
| Portal | "Primary Portal" with domain {SEED_PORTAL_SUBDOMAIN}.gxp.test (default: demo.gxp.test), default theme, and an HTML Page attached |
| HTML Page Plugin | A pre-built page plugin (html-page) with a .gxpapp bundle that gets processed and attached to the primary portal — lets you immediately visit the portal and see a working page |
| Plugins | Cloud-imported plugins (if PLUGIN_IMPORT_SEED_DISABLED=false and GCS auth is configured), plus default theme, dashboard masks, and welcome tile |
| Attendees | Sample attendee types and attendees on the primary project |
After seeding, visit https://demo.gxp.test to see the portal with the HTML page, or https://dashboard.gxp.test to log in to the admin dashboard.
Backup & Restore
# Before a risky operation
bin/gxp db:backup pre-migration
# If something goes wrong
bin/gxp db:restore pre-migration-20260408-143022.sql
# List all backups
bin/gxp db:list
Backups are stored in backups/ (gitignored).
Fresh Start
bin/gxp db:fresh # Drops all tables, re-migrates, re-seeds
Verification
After completing setup, verify the environment is fully operational.
Check All Containers Are Running
bin/gxp status
All services should show as running. If any show issues, check logs:
bin/gxp logs <service>
Check Application Responds
curl -sk -o /dev/null -w "%{http_code}" https://dashboard.gxp.test
# Expected: 200
Check Vite Dev Server
curl -sk -o /dev/null -w "%{http_code}" https://assets.gxp.test/@vite/client
# Expected: 200
Check Horizon (Queue Workers)
bin/gxp artisan horizon:status
# Expected: Horizon is running.
Access in Browser
| URL | Purpose |
|---|---|
https://dashboard.gxp.test | Admin Dashboard |
https://api.gxp.test | API endpoint |
https://assets.gxp.test | Vite asset server |
http://localhost:6006 | Storybook (when vite container is running) |
Success Criteria
- All containers are running (
bin/gxp statusshows healthy services) https://dashboard.gxp.testloads the login page without SSL warningshttps://assets.gxp.testproxies to the Vite dev server successfully- Horizon is running (
bin/gxp artisan horizon:statusreports running) - Database has been seeded (login with seeded credentials works)
Troubleshooting
502 Bad Gateway on first start
This is normal on a fresh build. RoadRunner/Octane needs 1-2 minutes to compile and warm up on the first request. Just wait and refresh. If it persists beyond 2 minutes, check bin/gxp logs app.
nginx won't start
Usually means an upstream can't be resolved. Make sure your modules match your enabled services:
bin/gxp configure # Regenerate nginx config
bin/gxp restart nginx
Port conflicts
# Check what's using ports 80/443
lsof -i tcp:80
lsof -i tcp:443
# Common fixes
sudo apachectl stop
sudo nginx -s stop
Vite manifest not found
bin/gxp npm install
# Or in host mode:
npm install && npm run build
Container won't start — need shell access
docker compose run --rm --entrypoint /bin/bash app
Node memory issues
NODE_OPTIONS="--max-old-space-size=4096" npm run build