feat(share): Rework `ddev share`, add cloudflared share provider, enhance tests, fixes #7784, fixes #6441
The Issue
- #7784 - Enhance
ddev shareto capture and expose URL to hooks and output - #6441 - Original issue about ddev share improvements
The ddev share command was hardcoded to only support ngrok and did not provide:
- A way to use alternative tunnel providers (like cloudflared)
- Access to the tunnel URL in hooks via
DDEV_SHARE_URLenvironment variable - Clear display of the tunnel URL to users
- Ability to customize or extend share providers
How This PR Solves The Issue
This PR completely refactors ddev share to use a script-based provider system that makes it extensible and user-customizable.
New Architecture
Provider Scripts (.ddev/share-providers/):
- Share providers are now simple bash scripts that output the tunnel URL to stdout
- Built-in providers:
ngrok.shandcloudflared.sh - Users can customize built-in providers or create their own
- Scripts receive environment variables:
DDEV_LOCAL_URL,DDEV_SHARE_ARGS, etc.
URL Capture and Hook Integration:
ddev sharecaptures the first line of stdout from the provider script as the tunnel URL- Sets
DDEV_SHARE_URLenvironment variable before runningpre-sharehooks - Displays tunnel URL prominently to users:
Tunnel URL: https://... - Runs
post-sharehooks after tunnel exits
Configuration:
- New config options:
share_default_provider,share_provider_args - Flag
--providerto override default - Priority:
--providerflag > config file > default (ngrok)
Key Features
✅ Cloudflared support - Free Cloudflare Tunnel, no account required
✅ Extensible - Users can create custom providers
✅ Hook integration - DDEV_SHARE_URL available to hooks for CMS config automation
✅ Customizable - Built-in providers can be modified by removing #ddev-generated
✅ Backward compatible - Existing ngrok usage continues to work
Manual Testing Instructions
Test Cloudflared Provider
-
Install cloudflared:
brew install cloudflared(macOS) or see https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation -
Create test project:
cd ~/tmp && mkdir test-share && cd test-share ddev config --project-type=php --php-version=8.3 echo '<?php phpinfo();' > index.php ddev start -
Test cloudflared:
ddev share --provider=cloudflaredVerify:
- URL displays:
Tunnel URL: https://xxx.trycloudflare.com - URL is accessible in browser
- Site content loads correctly
- Ctrl-C stops the tunnel
- URL displays:
Test Hook Integration
-
Add hooks to
.ddev/config.yaml:hooks: pre-share: - exec-host: 'echo "pre-share: URL=$DDEV_SHARE_URL"' post-share: - exec-host: 'echo "post-share: cleanup"' -
Run
ddev share --provider=cloudflared -
Verify:
- pre-share hook prints the tunnel URL
- post-share hook runs after Ctrl-C
Test Configuration
-
Set default provider:
ddev config --share-default-provider=cloudflared ddev share # Should use cloudflared -
Override with flag:
ddev share --provider=ngrok # Uses ngrok despite config
Test Custom Provider
-
Create custom provider:
cat > .ddev/share-providers/mock.sh << 'EOF' echo "Starting mock tunnel..." >&2 echo "https://mock.example.com" sleep 60 EOF chmod +x .ddev/share-providers/mock.sh -
Run:
ddev share --provider=mock -
Verify: Shows
Tunnel URL: https://mock.example.com
Automated Testing Overview
Comprehensive test suite in cmd/ddev/cmd/share_test.go:
TestShareCmdNgrok:
- Tests ngrok provider
- Verifies URL capture and display
- Makes HTTP request through tunnel to verify functionality
- Checks for expected WordPress content
TestShareCmdCloudflared:
- Tests cloudflared provider
- Verifies URL capture and display
- Makes HTTP request through tunnel to
/readme.html - Validates response contains expected content
TestShareCmdProviderSystem:
- Tests provider script discovery and execution
- Tests hook access to
DDEV_SHARE_URL - Tests provider priority (flag > config > default)
- Tests error handling (provider not found, not executable)
Run with: DDEV_TEST_SHARE_CMD=true go test -v -run TestShareCmd ./cmd/ddev/cmd
Note: Tests are skipped by default as they require ngrok/cloudflared installed and are slow.
Release/Deployment Notes
Breaking Changes: None - this is backward compatible
New Features:
- Cloudflared provider support (free alternative to ngrok)
- Script-based provider system for extensibility
DDEV_SHARE_URLenvironment variable in hooks- Configuration:
share_default_provider,share_ngrok_args,share_cloudflared_args --providerflag to select provider
User Benefits:
- Use cloudflared without account/auth required
- Automate CMS configuration updates via hooks (WordPress, TYPO3, etc.)
- Create custom tunnel providers
- Customize built-in providers
Documentation Needed:
.ddev/share-providers/README.txtincluded in PR- Consider docs update showing hook examples for common CMSs
Migration: Existing ngrok_args config continues to work (deprecated in favor of share_provider_args)
Download the artifacts for this pull request:
- all-ddev-executables.zip
- ddev-macos-amd64.zip
- ddev-macos-arm64.zip
- ddev-linux-arm64.zip
- ddev-linux-amd64.zip
- ddev-windows-amd64.zip
- ddev-windows-amd64-installer.zip
- ddev-windows-arm64.zip
- ddev-windows-arm64-installer.zip
See Testing a PR.
I tried adding share_default_provider: cloudflared to the global config but it didn't work.
It would be nice to have this available so all projects could use the "cloudflared" by default, rather then setting per-project.
Followups:
- [x] How to use static cloudflared URL
- [ ] Full info about pre_share and post_share hooks
- [ ] How to handle wordpress or typo3 with the new pre-share hook, https://github.com/ddev/ddev/issues/7799
- [ ] Implementing new share providers, maybe an example with localtunnel or something
- [ ] More about drupal multisite and similar
- [ ] update SO post about drupal multisite (which is also referenced in docs).
- [ ] Using router ports instead of direct-bind ports?
Please give this a try. I think we'll like it. I know you looked earlier @tyler36 , much appreciated, but I think it's matured since then. Added the global config for default that you asked for, and it can now do static URL with cloudflare.
Thanks @tyler36 ! @stasadev fixed most of the things you mentioned, but
I like the "quieter" output. Is there a way to turn the information back on for debugging purposes?
DDEV_VERBOSE=true ddev share ...
See if the docs in https://github.com/ddev/ddev/pull/7802/files#diff-391ba9f995a3b777282453d7c9a5afe7a108f1a40221413f4d432a1c685ba414R168-R176 are adequate, and whether we should be doing something more to make this obvious to people. I think it's important in a number of cases.
Another key thing to be done:
- [ ] Can we note the return value from the called binary and determine if it exited due to SIGINT, and if not, show the verbose captured output?
@nickchomey I'd love to have you take this for a spin, since you've pioneered so much in the cloudflared arena. It can now do static as well as dynamic URLs, and can take actions in the pre-share hook, which will make it possible to automatically process a WP project and fix it on exit.
I'll try to check it out soon!
I'll report back when ive had a chance to try this. But my main thoughts at a glance are:
- I wonder if the cloudflared docker image could (should?) be used instead of people needing to install the cloudflared binary on their system? The same could presumably be done for ngrok, with their image
- If number 1 is done, then it would open up other possibilities, such as integrating with traefik - i have some links here about that. It can even go so far as updating your actual cloudflare DNS records when logged into cloudflare, rather than just the quick tunnels with the random urls. I am quite certain that would be a very appealing feature for many (eg.
client1-demo.site.com) - This will never work with WordPress. They made the decision long ago to use absolute rather than relative paths, and this prevents using multiple hostnames/fqdns. I've documented in various places my excessive efforts to solve this via traefik middlewares rewriting request and response headers and body/payload, and it worked 99% of the time, but it was ultimately futile because of neverending edge cases. The only realistic way to use WP is to do search/replace in the database for the old/new url (this is the best tool for that, as it is a) written in Go and b) handles serialized php array strings, among other things)
Thanks @nickchomey - the warning about your group ID is about (previous) misconfiguration of your machine, I think. GID 1000 should work for everybody on a normally configured linux machine.
Are there any thoughts about my previous comment about using docker rather than binaries?
I'll report back when ive had a chance to try this. But my main thoughts at a glance are:
- I wonder if the cloudflared docker image could (should?) be used instead of people needing to install the cloudflared binary on their system? The same could presumably be done for ngrok, with their image
- If number 1 is done, then it would open up other possibilities, such as integrating with traefik - i have some links here about that. It can even go so far as updating your actual cloudflare DNS records when logged into cloudflare, rather than just the quick tunnels with the random urls. I am quite certain that would be a very appealing feature for many (eg.
client1-demo.site.com)
Even if the answer is simply "no thanks", I'd be interested to know if it is "because we're already invested in this path" or if there's a technical reason why the containers wouldn't actually be more compatible with DDEV and easier for users
Hi @nickchomey - Your suggestion is obviously an alternate implementation technique, but what we have here is a PR that is in play. I'm not tempted to re-implement from scratch based on the comment. I haven't experimented with your suggested approach.
Totally understandable! I was just sharing what I learned from my "pioneering" work on the topic. If WordPress were compatible with all of this, I'd certainly have switched my addon from the cloudflared binary to using docker + traefik + dns integration.
I look forward to seeing how this all turns out, even if I won't be using it.
I haven't experimented yet, but WP should work fine by using the pre-share hook to do the normal ddev wp search-replace, If you're interested in trying that out with this, it will be welcome. Something like
hooks:
pre-share:
- exec: wp search-replace ${DDEV_PRIMARY_URL} ${DDEV_SHARE_URL}
post-share:
- exec: wp search-replace ${DDEV_SHARE_URL} ${DDEV_PRIMARY_URL}
post-share could also load a dump instead of search replace, and pre-share could do a dump or snapshot
Yeah, i suppose that can work. Though, the wp-cli search-replace tool isnt as good as the tool I mentioned above (which is developed by the owners of WordPress) because it doesn't handle serialized php arrays rename and is slow.
Though, it works better for me to just do a proper CF tunnel with a subdomain of the site's actual domain and keep it there always. I just put an IP address filter on the domain. Of course, that makes it clumsier than what you've proposed if you want to share with others.
Anyway, i'm glad to see this coming to fruition!
I struggled with cloudflared testing locally, because Cloudflare Tunnels are blocked here, and even under VPN I was getting:
$ ddev share --provider=cloudflared
Starting cloudflared tunnel...
Tunnel URL: https://utah-lately-beneath-verification.trycloudflare.com
2025-12-15T19:30:17Z ERR Failed to dial a quic connection error="failed to dial to edge with quic: timeout: no recent network activity" connIndex=0 event=0 ip=198.41.192.37
It seems like VPN handles only tcp traffic, not quic. But finally I managed to get around it with:
ddev share --provider=cloudflared --provider-args='--protocol http2'
About showing errors, I disabled internet, and ran ddev share, it wasn't obvious what's going on here.
It would help to make the output more verbose for each case and show the actual command being run.
It may also be useful to say:
Running script from .ddev/share-providers/ngrok.sh
I think that many people don't know what ddev share does and see it as some kind of magic. We should explain what is happening so they can use it or debug it outside DDEV, and not report issues to us when the problem is not on our side.
No output for 30 seconds:
$ ddev share
Starting ngrok tunnel...
Only then I got this, and it's still "running":
$ ddev share
Starting ngrok tunnel...
Error: Failed to get ngrok URL after 30 seconds
^C
This one is better, but still not good enough:
$ ddev share --provider=cloudflared
Starting cloudflared tunnel...
Tunnel URL: https://api.trycloudflare.com
Provider 'cloudflared' exited with code 1
For details, run: DDEV_DEBUG=true ddev share --provider=cloudflared
Duplicating my previous comment about pkg/ddevapp/dotddev_assets/share-providers/README.txt, so it doesn't get lost:
I think this README should be copied somewhere in the docs, because we don't have anything there for DDEV_LOCAL_URL, DDEV_SHARE_URL, DDEV_SHARE_ARGS