Skip to content

Instantly share code, notes, and snippets.

@saifsmailbox98
Last active April 14, 2026 15:21
Show Gist options
  • Select an option

  • Save saifsmailbox98/12f77b806e0823122fc6997b827e544b to your computer and use it in GitHub Desktop.

Select an option

Save saifsmailbox98/12f77b806e0823122fc6997b827e544b to your computer and use it in GitHub Desktop.

Revisions

  1. saifsmailbox98 revised this gist Apr 14, 2026. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions extinguisher.md
    Original 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`
  2. saifsmailbox98 created this gist Apr 14, 2026.
    766 changes: 766 additions & 0 deletions extinguisher.md
    Original 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 |