Last active
April 14, 2026 15:21
-
-
Save saifsmailbox98/12f77b806e0823122fc6997b827e544b to your computer and use it in GitHub Desktop.
Revisions
-
saifsmailbox98 revised this gist
Apr 14, 2026 . 1 changed file with 2 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,5 +1,7 @@ # Enhanced Dev Environment Setup > Not responsible for melted desks, burnt thighs, or AppleCare claims. This guide sets up an improved local development environment with: - **just** as a task runner (replacement for make) - **portless** for `.test` domain names instead of `localhost:port` -
saifsmailbox98 created this gist
Apr 14, 2026 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,766 @@ # Enhanced Dev Environment Setup This guide sets up an improved local development environment with: - **just** as a task runner (replacement for make) - **portless** for `.test` domain names instead of `localhost:port` - **Git worktrees** for working on multiple tasks in parallel, each with its own isolated Docker stack - **mitmproxy** for inspecting HTTP requests between frontend and backend - **Mailpit** as a standalone SMTP server shared across all stacks - **tsgo** (native TypeScript compiler) for 10x faster type checking --- ## Prerequisites ```bash brew install just npm install -g portless ``` ### Just shell completions (zsh) ```bash mkdir -p ~/.zsh/completions just --completions zsh > ~/.zsh/completions/_just ``` Add to `~/.zshrc`: ```bash fpath=(~/.zsh/completions $fpath) autoload -U compinit compinit ``` ### Start portless proxy (one-time, persists in background) ```bash portless proxy start --tld test ``` ### Docker Desktop settings (recommended) - Disk image size: **120 GB** (Settings → Resources) - Memory: **16 GB** (Settings → Resources) --- ## Files to Create All files are local to your clone — nothing gets committed. They are ignored via `.git/info/exclude`. ### 1. `.git/info/exclude` Append these lines to your existing `.git/info/exclude`: ``` # Personal dev setup docker-compose.dev.override.yml docker-compose.worktree.yml docker-compose.worktree.debug.yml justfile README.dev.md nginx/default.dev.debug.conf ``` ### 2. `justfile` (project root) ```just # Default project name is the current directory name default_name := `basename "$PWD"` # --- Main dev environment (existing workflow) --- up-dev: #!/usr/bin/env bash set -euo pipefail # Update .env with the correct site URL grep -q '^SITE_URL=' .env && sed -i '' 's|^SITE_URL=.*|SITE_URL=https://infisical.test|' .env || echo 'SITE_URL=https://infisical.test' >> .env grep -q '^VITE_ALLOWED_HOSTS=' .env && sed -i '' 's|^VITE_ALLOWED_HOSTS=.*|VITE_ALLOWED_HOSTS=infisical.test|' .env || echo 'VITE_ALLOWED_HOSTS=infisical.test' >> .env docker compose -f docker-compose.dev.yml -f docker-compose.dev.override.yml up --build -d portless alias infisical 8080 --tld test --force 2>/dev/null || true portless alias db.infisical 5432 --tld test --force 2>/dev/null || true portless alias redis.infisical 6379 --tld test --force 2>/dev/null || true @echo "" @echo "App: https://infisical.test" @echo "Postgres: db.infisical.test (port 5432)" @echo "Redis: redis.infisical.test (port 6379)" @echo "psql: psql -h db.infisical.test -U infisical -d infisical" up-dev-ldap: docker compose -f docker-compose.dev.yml -f docker-compose.dev.override.yml --profile ldap up --build -d portless alias infisical 8080 --tld test --force 2>/dev/null || true portless alias db.infisical 5432 --tld test --force 2>/dev/null || true portless alias redis.infisical 6379 --tld test --force 2>/dev/null || true @echo "" @echo "App: https://infisical.test" @echo "Postgres: db.infisical.test (port 5432)" @echo "Redis: redis.infisical.test (port 6379)" up-dev-metrics: docker compose -f docker-compose.dev.yml -f docker-compose.dev.override.yml --profile metrics up --build -d portless alias infisical 8080 --tld test --force 2>/dev/null || true portless alias db.infisical 5432 --tld test --force 2>/dev/null || true portless alias redis.infisical 6379 --tld test --force 2>/dev/null || true @echo "" @echo "App: https://infisical.test" @echo "Postgres: db.infisical.test (port 5432)" @echo "Redis: redis.infisical.test (port 6379)" up-dev-sso: docker compose -f docker-compose.dev.yml -f docker-compose.dev.override.yml --profile sso up --build -d portless alias infisical 8080 --tld test --force 2>/dev/null || true portless alias db.infisical 5432 --tld test --force 2>/dev/null || true portless alias redis.infisical 6379 --tld test --force 2>/dev/null || true @echo "" @echo "App: https://infisical.test" @echo "Postgres: db.infisical.test (port 5432)" @echo "Redis: redis.infisical.test (port 6379)" up-prod: docker compose -f docker-compose.prod.yml up --build down-dev: docker compose -f docker-compose.dev.yml down reviewable-ui: cd frontend && npm run lint:fix && npm run type:check reviewable-api: cd backend && npm run lint:fix && npm run type:check reviewable: reviewable-ui reviewable-api # --- Worktree stacks --- # Start a full isolated stack for the current worktree # Usage: just up [name] [--no-fips] up name=default_name *flags="": #!/usr/bin/env bash set -euo pipefail NAME="{{name}}" PG_SOURCE="infisical_postgres-data1" # Check for --no-fips flag for flag in {{flags}}; do if [ "$flag" = "--no-fips" ]; then PG_SOURCE="infisical_postgres-data" fi done PG_VOLUME="${NAME}_postgres-data" # Clone the postgres volume if this task doesn't have one yet if ! docker volume inspect "$PG_VOLUME" > /dev/null 2>&1; then echo "Cloning $PG_SOURCE → $PG_VOLUME..." docker volume create "$PG_VOLUME" > /dev/null docker run --rm \ -v "$PG_SOURCE":/from:ro \ -v "$PG_VOLUME":/to \ alpine sh -c "cp -a /from/. /to/" echo "Done." else echo "Volume $PG_VOLUME already exists, reusing." fi # Update .env with the correct site URL if [ -f .env ]; then grep -q '^SITE_URL=' .env && sed -i '' "s|^SITE_URL=.*|SITE_URL=https://$NAME.test|" .env || echo "SITE_URL=https://$NAME.test" >> .env grep -q '^VITE_ALLOWED_HOSTS=' .env && sed -i '' "s|^VITE_ALLOWED_HOSTS=.*|VITE_ALLOWED_HOSTS=$NAME.test|" .env || echo "VITE_ALLOWED_HOSTS=$NAME.test" >> .env fi # Start the stack PG_VOLUME="$PG_VOLUME" docker compose \ -p "$NAME" \ -f docker-compose.worktree.yml \ up --build -d # Wait a moment for containers to get ports assigned sleep 2 # Get the nginx container's host port NGINX_CONTAINER="${NAME}-nginx-1" NGINX_PORT=$(docker port "$NGINX_CONTAINER" 80 2>/dev/null | head -1 | cut -d: -f2) # Get the postgres container's host port DB_CONTAINER="${NAME}-db-1" DB_PORT=$(docker port "$DB_CONTAINER" 5432 2>/dev/null | head -1 | cut -d: -f2) # Get the redis container's host port REDIS_CONTAINER="${NAME}-redis-1" REDIS_PORT=$(docker port "$REDIS_CONTAINER" 6379 2>/dev/null | head -1 | cut -d: -f2) # Register with portless portless alias "$NAME" "$NGINX_PORT" --tld test --force 2>/dev/null || true portless alias "db.$NAME" "$DB_PORT" --tld test --force 2>/dev/null || true portless alias "redis.$NAME" "$REDIS_PORT" --tld test --force 2>/dev/null || true echo "" echo "Stack '$NAME' is running:" echo " App: https://$NAME.test" echo " Postgres: db.$NAME.test (port $DB_PORT)" echo " Redis: redis.$NAME.test (port $REDIS_PORT)" echo " psql: psql -h db.$NAME.test -U infisical -d infisical" # Stop a worktree stack (keeps volumes) down name=default_name: #!/usr/bin/env bash set -euo pipefail NAME="{{name}}" docker compose \ -p "$NAME" \ -f docker-compose.worktree.yml \ -f docker-compose.worktree.debug.yml \ down 2>/dev/null || \ docker compose \ -p "$NAME" \ -f docker-compose.worktree.yml \ down # Remove portless aliases portless alias --remove "$NAME" 2>/dev/null || true portless alias --remove "debug.$NAME" 2>/dev/null || true portless alias --remove "db.$NAME" 2>/dev/null || true portless alias --remove "redis.$NAME" 2>/dev/null || true echo "Stack '$NAME' stopped. Volumes preserved." # Remove a worktree stack's containers AND volumes rm name=default_name: #!/usr/bin/env bash set -euo pipefail NAME="{{name}}" docker compose \ -p "$NAME" \ -f docker-compose.worktree.yml \ -f docker-compose.worktree.debug.yml \ down -v 2>/dev/null || \ docker compose \ -p "$NAME" \ -f docker-compose.worktree.yml \ down -v # Remove the cloned postgres volume docker volume rm "${NAME}_postgres-data" 2>/dev/null || true # Remove portless aliases portless alias --remove "$NAME" 2>/dev/null || true portless alias --remove "debug.$NAME" 2>/dev/null || true portless alias --remove "db.$NAME" 2>/dev/null || true portless alias --remove "redis.$NAME" 2>/dev/null || true echo "Stack '$NAME' removed (containers + volumes)." # Start a worktree stack with mitmproxy (request inspector) up-debug name=default_name *flags="": #!/usr/bin/env bash set -euo pipefail NAME="{{name}}" PG_SOURCE="infisical_postgres-data1" # Check for --no-fips flag for flag in {{flags}}; do if [ "$flag" = "--no-fips" ]; then PG_SOURCE="infisical_postgres-data" fi done PG_VOLUME="${NAME}_postgres-data" # Clone the postgres volume if this task doesn't have one yet if ! docker volume inspect "$PG_VOLUME" > /dev/null 2>&1; then echo "Cloning $PG_SOURCE → $PG_VOLUME..." docker volume create "$PG_VOLUME" > /dev/null docker run --rm \ -v "$PG_SOURCE":/from:ro \ -v "$PG_VOLUME":/to \ alpine sh -c "cp -a /from/. /to/" echo "Done." else echo "Volume $PG_VOLUME already exists, reusing." fi # Update .env with the correct site URL if [ -f .env ]; then grep -q '^SITE_URL=' .env && sed -i '' "s|^SITE_URL=.*|SITE_URL=https://$NAME.test|" .env || echo "SITE_URL=https://$NAME.test" >> .env grep -q '^VITE_ALLOWED_HOSTS=' .env && sed -i '' "s|^VITE_ALLOWED_HOSTS=.*|VITE_ALLOWED_HOSTS=$NAME.test|" .env || echo "VITE_ALLOWED_HOSTS=$NAME.test" >> .env fi # Start the stack with mitmproxy PG_VOLUME="$PG_VOLUME" docker compose \ -p "$NAME" \ -f docker-compose.worktree.yml \ -f docker-compose.worktree.debug.yml \ up --build -d # Wait a moment for containers to get ports assigned sleep 2 # Get the nginx container's host port NGINX_CONTAINER="${NAME}-nginx-1" NGINX_PORT=$(docker port "$NGINX_CONTAINER" 80 2>/dev/null | head -1 | cut -d: -f2) # Get the mitmweb UI port MITM_CONTAINER="${NAME}-mitmproxy-1" MITM_PORT=$(docker port "$MITM_CONTAINER" 8081 2>/dev/null | head -1 | cut -d: -f2) # Get the postgres container's host port DB_CONTAINER="${NAME}-db-1" DB_PORT=$(docker port "$DB_CONTAINER" 5432 2>/dev/null | head -1 | cut -d: -f2) # Get the redis container's host port REDIS_CONTAINER="${NAME}-redis-1" REDIS_PORT=$(docker port "$REDIS_CONTAINER" 6379 2>/dev/null | head -1 | cut -d: -f2) # Register with portless portless alias "$NAME" "$NGINX_PORT" --tld test --force 2>/dev/null || true portless alias "debug.$NAME" "$MITM_PORT" --tld test --force 2>/dev/null || true portless alias "db.$NAME" "$DB_PORT" --tld test --force 2>/dev/null || true portless alias "redis.$NAME" "$REDIS_PORT" --tld test --force 2>/dev/null || true echo "" echo "Stack '$NAME' is running with mitmproxy:" echo " App: https://$NAME.test" echo " mitmweb: https://debug.$NAME.test (request inspector, password: password)" echo " Postgres: db.$NAME.test (port $DB_PORT)" echo " Redis: redis.$NAME.test (port $REDIS_PORT)" echo " psql: psql -h db.$NAME.test -U infisical -d infisical" # List all running worktree stacks ls: @docker ps --filter "label=com.docker.compose.project" --format "table {{{{.Names}}}}\t{{{{.Status}}}}\t{{{{.Ports}}}}" | head -50 # --- Standalone tools --- mailpit: docker run -d --name mailpit -p 1025:1025 -p 8025:8025 --restart unless-stopped axllent/mailpit mailpit-stop: docker stop mailpit && docker rm mailpit # --- TypeScript native compiler (tsgo) --- tsgo-fe: cd frontend && npm install --save-dev @typescript/native-preview@latest tsgo-be: cd backend && npm install --save-dev @typescript/native-preview@latest tsgo: tsgo-fe tsgo-be ``` ### 3. `docker-compose.dev.override.yml` (project root) Profiles out pgAdmin, redis-commander, and smtp-server from the default dev stack so they only start with `--profile tools`. ```yaml services: pgadmin: profiles: [tools] redis-commander: profiles: [tools] smtp-server: profiles: [tools] ``` ### 4. `docker-compose.worktree.yml` (project root) Full isolated stack for worktrees. All host ports are random (`0:port`) to avoid conflicts when running multiple stacks. ```yaml services: nginx: image: nginx restart: "always" ports: - "0:80" volumes: - ./nginx/default.dev.conf:/etc/nginx/conf.d/default.conf:ro depends_on: - backend - frontend db: image: postgres:14-alpine ports: - "0:5432" volumes: - postgres_data:/var/lib/postgresql/data environment: POSTGRES_PASSWORD: infisical POSTGRES_USER: infisical POSTGRES_DB: infisical redis: image: redis environment: - ALLOW_EMPTY_PASSWORD=yes ports: - "0:6379" volumes: - redis_data:/data clickhouse: image: clickhouse/clickhouse-server:25.12.5 restart: unless-stopped ports: - "0:8123" - "0:9000" volumes: - clickhouse_data:/var/lib/clickhouse - clickhouse_logs:/var/log/clickhouse-server environment: - CLICKHOUSE_DB=infisical - CLICKHOUSE_USER=infisical - CLICKHOUSE_PASSWORD=infisical - CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1 ulimits: nofile: soft: 262144 hard: 262144 backend: image: infisical-dev-backend:latest build: context: ./backend dockerfile: Dockerfile.dev.fips depends_on: db: condition: service_started redis: condition: service_started clickhouse: condition: service_started env_file: - .env ports: - "0:4000" environment: - NODE_ENV=development - DB_CONNECTION_URI=postgres://infisical:infisical@db/infisical?sslmode=disable - TELEMETRY_ENABLED=false volumes: - ./backend/src:/app/src - softhsm_tokens:/etc/softhsm2/tokens extra_hosts: - "host.docker.internal:host-gateway" frontend: image: infisical-dev-frontend:latest restart: unless-stopped depends_on: - backend build: context: ./frontend dockerfile: Dockerfile.dev volumes: - ./frontend/src:/app/src/ - ./frontend/public:/app/public env_file: .env volumes: postgres_data: external: true name: ${PG_VOLUME} redis_data: driver: local clickhouse_data: driver: local clickhouse_logs: driver: local softhsm_tokens: driver: local ``` ### 5. `docker-compose.worktree.debug.yml` (project root) Compose override that adds mitmproxy between nginx and backend for request inspection. ```yaml services: mitmproxy: image: mitmproxy/mitmproxy command: mitmweb --mode reverse:http://backend:4000 --web-host 0.0.0.0 --listen-port 4000 --no-web-open-browser --set web_password=password restart: unless-stopped ports: - "0:8081" depends_on: - backend nginx: volumes: - ./nginx/default.dev.debug.conf:/etc/nginx/conf.d/default.conf:ro ``` ### 6. `nginx/default.dev.debug.conf` Same as `nginx/default.dev.conf` but routes API traffic through mitmproxy instead of directly to backend. Only difference is line 2: ```nginx upstream api { server mitmproxy:4000; } server { listen 80; large_client_header_buffers 8 128k; client_header_buffer_size 128k; # WebSocket endpoint for PAM web access location ~ ^/api/v1/pam/accounts/[^/]+/web-access$ { proxy_set_header X-Real-RIP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_set_header X-NginX-Proxy true; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_buffering off; proxy_cache off; proxy_read_timeout 4200s; proxy_send_timeout 4200s; proxy_pass http://api; proxy_redirect off; proxy_cookie_path / "/; SameSite=strict"; } location ~ ^/(api|secret-scanning/webhooks) { proxy_set_header X-Real-RIP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_set_header X-NginX-Proxy true; proxy_pass http://api; proxy_redirect off; proxy_cookie_path / "/; SameSite=strict"; } location /runtime-ui-env.js { proxy_set_header X-Real-RIP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_set_header X-NginX-Proxy true; proxy_pass http://api; proxy_redirect off; proxy_cookie_path / "/; HttpOnly; SameSite=strict"; } location /api/v3/migrate { client_max_body_size 25M; proxy_set_header X-Real-RIP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_set_header X-NginX-Proxy true; proxy_pass http://api; proxy_redirect off; proxy_cookie_path / "/; HttpOnly; SameSite=strict"; } location /scep { proxy_set_header X-Real-RIP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_set_header X-NginX-Proxy true; proxy_pass http://api; proxy_redirect off; proxy_cookie_path / "/; HttpOnly; SameSite=strict"; } location ~ /\.well-known { proxy_set_header X-Real-RIP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_set_header X-NginX-Proxy true; proxy_set_header X-SSL-Client-Cert $ssl_client_escaped_cert; proxy_pass http://api; proxy_redirect off; proxy_cookie_path / "/; HttpOnly; SameSite=strict"; } location / { include /etc/nginx/mime.types; proxy_set_header X-Real-RIP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_set_header X-NginX-Proxy true; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_pass http://frontend:3000; proxy_redirect off; } } ``` ### 7. `.git/local-hooks/post-checkout` Git hook that automatically copies all dev files into new worktrees. Must be executable (`chmod +x`). ```bash #!/bin/bash OLD_REF=$1 # Only run for new worktrees (old ref is all zeros) if [ "$OLD_REF" = "0000000000000000000000000000000000000000" ]; then MAIN_REPO="$(git rev-parse --git-common-dir)/.." WORKTREE="$(git rev-parse --show-toplevel)" cp "$MAIN_REPO/justfile" "$WORKTREE/" 2>/dev/null cp "$MAIN_REPO/docker-compose.worktree.yml" "$WORKTREE/" 2>/dev/null cp "$MAIN_REPO/docker-compose.worktree.debug.yml" "$WORKTREE/" 2>/dev/null cp "$MAIN_REPO/docker-compose.dev.override.yml" "$WORKTREE/" 2>/dev/null cp "$MAIN_REPO/README.dev.md" "$WORKTREE/" 2>/dev/null cp "$MAIN_REPO/.env" "$WORKTREE/" 2>/dev/null mkdir -p "$WORKTREE/nginx" cp "$MAIN_REPO/nginx/default.dev.debug.conf" "$WORKTREE/nginx/" 2>/dev/null echo "Dev files copied into worktree: $WORKTREE" fi ``` ### 8. Git hooks path config Husky overrides the default hooks directory. To use our custom hook alongside Husky's `pre-commit`: ```bash # Create local hooks directory mkdir -p .git/local-hooks/_ # Copy Husky's pre-commit hook and helper cp .husky/pre-commit .git/local-hooks/ cp .husky/_/husky.sh .git/local-hooks/_/ # Copy the post-checkout hook (from step 7 above) # Make it executable chmod +x .git/local-hooks/post-checkout chmod +x .git/local-hooks/pre-commit # Point git to the local hooks directory git config --local core.hooksPath .git/local-hooks ``` --- ## Workflow ### Main repo (day-to-day) ```bash just up-dev # → https://infisical.test just down-dev # stop ``` ### New task in a worktree ```bash # Create worktree (hook auto-copies dev files + .env) git fetch origin git worktree add ../my-feature -b feat/my-feature origin/main cd ../my-feature # Optional: enable fast TS compiler just tsgo # Start isolated stack just up # → https://my-feature.test ``` ### Review a PR ```bash git fetch origin git worktree add ../review-pr origin/feat/some-feature cd ../review-pr just up # → https://review-pr.test ``` ### Multiple stacks simultaneously Both run at the same time on different domains: - `https://my-feature.test` - `https://review-pr.test` ### Inspect API requests ```bash just up-debug # → app at https://my-feature.test # → mitmweb UI at https://debug.my-feature.test (password: password) ``` ### Stop / clean up ```bash just down # stop containers, keep volumes (resume later) just up # resume with same DB state just rm # remove containers + volumes (permanent) # Remove the worktree cd ~/infi/repos/infisical git worktree remove ../review-pr ``` ### Standalone mailpit (shared SMTP) ```bash just mailpit # start — SMTP on 1025, UI on http://localhost:8025 just mailpit-stop # stop ``` --- ## All Commands | Command | What it does | |---|---| | `just up-dev` | Start main dev stack → `https://infisical.test` | | `just up-dev-ldap` | Start with LDAP profile | | `just up-dev-sso` | Start with SSO profile | | `just up-dev-metrics` | Start with metrics profile | | `just down-dev` | Stop main dev stack | | `just up` | Start worktree stack (uses directory name) | | `just up my-name` | Start with custom name | | `just up --no-fips` | Clone non-FIPS postgres volume | | `just up-debug` | Start with mitmproxy request inspector | | `just down` | Stop worktree stack, keep volumes | | `just rm` | Remove worktree stack + volumes | | `just ls` | List all running stacks | | `just mailpit` | Start standalone mailpit | | `just mailpit-stop` | Stop mailpit | | `just tsgo` | Install latest tsgo in both frontend and backend | | `just tsgo-fe` | Install latest tsgo in frontend only | | `just tsgo-be` | Install latest tsgo in backend only | | `just reviewable` | Lint + typecheck both frontend and backend | | `just reviewable-ui` | Lint + typecheck frontend | | `just reviewable-api` | Lint + typecheck backend |