Skip to main content
Version: v1 (Current)

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

ToolRequiredNotes
Docker DesktopYesIncludes Docker Compose. Allocate 12 GB+ RAM (8 GB minimum), 2+ CPUs.
mkcertYesbrew install mkcert — local SSL certificate generation
GitYesFor submodules and hooks
Node.jsOnly for host-mode ViteSee .nvmrc for version
dnsmasqRecommendedbrew 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:

  1. Prerequisites check — verifies Docker, Docker Compose, mkcert, and Git
  2. Module configuration — choose which optional services to enable (creates modules.env)
  3. Environment file — generates .env from .env.example, sets COMPOSE_PROFILES
  4. Docker Compose — copies docker-compose.yaml.example to docker-compose.yaml
  5. SSL certificates — generates *.gxp.test wildcard certs via mkcert
  6. Nginx config — generates nginx.local.conf from templates, deploys site configs to sites-enabled/
  7. DNS — offers dnsmasq (recommended) or /etc/hosts setup
  8. Git submodule — initializes the documentation/ submodule
  9. Git hooks — configures core.hooksPath to githooks/
  10. 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

ModuleDocker ServicesNginx ConfigsDomains / Ports
Core (always)app, workers, reverb, nginx, database, redisgxp.test.confgxp.test, dashboard.gxp.test, api.gxp.test, assets.gxp.test
VITE_MODE=dockervite(included in gxp.test.conf)
MODULE_WEBRTCmediasoup, coturnmediasoup.conf, turn.confmedias.gxp.test, turn.gxp.test
MODULE_DBTOOLSdatabase-expose (socat proxy)localhost:54320
MODULE_DOCSdocsdocs.confdocs.gxp.test
MODULE_TESTINGselenium

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

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_PATH in .env controls which directory is mounted as /app
  • The database (named volume gxp-pgdata) persists across switches
  • bin/gxp switch handles: 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:

WhatDetails
Teams10 teams including "Gramercy Tech" as the primary team
Admin AccountUses SEED_ADMIN_EMAIL / SEED_ADMIN_PASSWORD from .env. Super admin with admin + developer roles on the primary team.
Other Admins10 additional admin accounts attached to all teams
Permissions & RolesFull 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 PluginA 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
PluginsCloud-imported plugins (if PLUGIN_IMPORT_SEED_DISABLED=false and GCS auth is configured), plus default theme, dashboard masks, and welcome tile
AttendeesSample 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

URLPurpose
https://dashboard.gxp.testAdmin Dashboard
https://api.gxp.testAPI endpoint
https://assets.gxp.testVite asset server
http://localhost:6006Storybook (when vite container is running)

Success Criteria

  • All containers are running (bin/gxp status shows healthy services)
  • https://dashboard.gxp.test loads the login page without SSL warnings
  • https://assets.gxp.test proxies to the Vite dev server successfully
  • Horizon is running (bin/gxp artisan horizon:status reports 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