Skip to content

Enhance ddev share to capture and expose ngrok URL to hooks and output #7784

@rfay

Description

@rfay

Problem Statement

When using ddev share, the ngrok public URL is only displayed in ngrok's complex terminal output. This creates challenges for:

  1. CMS configuration: Systems like TYPO3, WordPress, and others need the base URL updated in their configuration files when sharing
  2. Automation: Hooks cannot access the ngrok URL because pre-share runs before ngrok starts
  3. User experience: The actual share URL is buried in ngrok's verbose output, making it hard to find and copy

Current Behavior

// cmd/ddev/cmd/share.go (simplified flow)
ProcessHooks("pre-share")  // Runs BEFORE ngrok starts - URL not available
StartNgrok()               // Ngrok URL generated here
WaitForExit()             
ProcessHooks("post-share") // Runs AFTER ngrok exits - too late

Proposed Enhancement

Modify ddev share to:

  1. Start ngrok
  2. Poll ngrok's local API (localhost:4040/api/tunnels) to capture the public URL
  3. Export the URL as an environment variable (DDEV_SHARE_URL)
  4. Move pre-share hook execution to after capturing the URL (or create a new post-share-start hook)
  5. Display the URL prominently in output
  6. Clean up on exit

Implementation Approach

// After starting ngrok (around line 79 in share.go)
util.Success("Running %s %s", ngrokLoc, strings.Join(ngrokArgs, " "))

// Wait for ngrok to be ready and get the public URL
var publicURL string
for i := 0; i < 10; i++ {
    time.Sleep(500 * time.Millisecond)
    publicURL, err = getNgrokPublicURL()
    if err == nil {
        break
    }
}

if publicURL != "" {
    os.Setenv("DDEV_SHARE_URL", publicURL)
    util.Success("╔════════════════════════════════════════════════════════")
    util.Success("║ ngrok tunnel active at:")
    util.Success("║ %s", publicURL)
    util.Success("╚════════════════════════════════════════════════════════")
    
    // NOW run pre-share hooks with URL available
    err = app.ProcessHooks("pre-share")
    if err != nil {
        util.Failed("Failed to process pre-share hooks: %v", err)
    }
}

// Helper function
func getNgrokPublicURL() (string, error) {
    resp, err := http.Get("http://localhost:4040/api/tunnels")
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()
    
    var result struct {
        Tunnels []struct {
            PublicURL string `json:"public_url"`
        } `json:"tunnels"`
    }
    
    if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
        return "", err
    }
    
    if len(result.Tunnels) > 0 {
        return result.Tunnels[0].PublicURL, nil
    }
    
    return "", fmt.Errorf("no tunnels found")
}

Benefits

1. CMS Configuration Automation

TYPO3 example using .env file:

# .ddev/config.yaml
hooks:
  pre-share:
    - exec-host: |
        # Update .env file with ngrok URL
        if [ ! -z "$DDEV_SHARE_URL" ]; then
          ddev dotenv set BASE_DOMAIN="${DDEV_SHARE_URL#https://}"
          echo "Updated BASE_DOMAIN to $DDEV_SHARE_URL"
        fi
  
  post-share:
    - exec-host: |
        # Restore original domain
        ddev dotenv set BASE_DOMAIN="$(ddev describe -j | jq -r .raw.primary_url | sed 's|https\?://||')"

TYPO3 site config (config/sites/main/config.yaml):

base: 'https://%env(BASE_DOMAIN)%/'

2. WordPress Configuration

hooks:
  pre-share:
    - exec: "wp search-replace '${DDEV_PRIMARY_URL}' '${DDEV_SHARE_URL}' --skip-columns=guid"
  
  post-share:
    - exec: "wp search-replace '${DDEV_SHARE_URL}' '${DDEV_PRIMARY_URL}' --skip-columns=guid"

3. Better User Experience

Clean, prominent output:

Starting ddev-d11-web ... done
Running ngrok http http://d11-web:80
╔════════════════════════════════════════════════════════
║ ngrok tunnel active at:
║ https://abc123.ngrok-free.app
╚════════════════════════════════════════════════════════

Your site is now publicly accessible!
Press Ctrl-C to stop sharing.

4. Documentation & Testing

Hooks can save the URL for documentation or automated testing:

hooks:
  pre-share:
    - exec-host: echo "$DDEV_SHARE_URL" > .ddev/share-url.txt
    - exec-host: "curl -s '$DDEV_SHARE_URL' > /dev/null && echo 'Site is accessible!'"

Alternative Considered

Reserved ngrok domains: Users can use ngrok_args: "--domain my-reserved.ngrok-free.app" with a free static domain, but this:

  • Requires manual ngrok account configuration
  • Limits users to one static domain (ngrok free tier)
  • Still doesn't make the URL easily visible in output

Related Issues

This enhancement would solve similar problems for:

  • Drupal (base URL configuration)
  • Laravel (APP_URL in .env)
  • Symfony (various URL configurations)
  • Magento (base URL settings)
  • Any CMS that needs URL configuration updates

Implementation Checklist

  • Add getNgrokPublicURL() helper function
  • Modify share flow to capture URL after ngrok starts
  • Export DDEV_SHARE_URL environment variable for hooks
  • Reorder hook execution (pre-share after URL capture)
  • Add prominent URL display in output
  • Update documentation with hook examples
  • Add tests for URL capture functionality
  • Consider adding ngrok_port config option (default: 4040) for custom ngrok API ports

Documentation Updates Needed

  • Update hooks.md with DDEV_SHARE_URL variable documentation
  • Add CMS-specific examples (TYPO3, WordPress, etc.)
  • Document ngrok API port configuration if customizable
  • Add troubleshooting section for cases where URL capture fails

Created with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    Done

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions