What You'll Build
When a visitor submits a contact form on WordPress, send a custom event to Matomo analytics - all running on the same server. No third-party services, no custom code, just Stacker PIPEs.
WordPress form → Stacker PIPE → Matomo tracking event
Quick Answer
# Deploy both apps
stacker deploy --target cloud --force-rebuild
# Fix container naming (required workaround)
sed -i 's/^ app:/ wordpress-matomo:/' .stacker/docker-compose.yml
# Bridge Docker networks
docker network connect project_app-network statuspanel_agent
# Scan and create pipe
stacker pipe scan --app wordpress-matomo --container project-wordpress-matomo-1
stacker pipe create wordpress-matomo matomo
Three workarounds needed - but the pipe works end-to-end.
Step 1: Deploy Both Apps
We created a combined project wordpress-matomo with four containers in a single stacker.yml:
- wordpress-matomo - WordPress with Contact Form 7
- wp-db - MySQL 8.0 for WordPress
- matomo - Matomo analytics
- matomo-db - MariaDB 11 for Matomo
stacker deploy --target cloud --force-rebuild
One deploy put everything on a Hetzner cloud server. Worked flawlessly on the first try.
Step 2: Set Up the Form
Install Contact Form 7 on WordPress and create a form with name, email, and message fields. We also added a plain HTML form on the homepage to ensure the agent could detect it.
# Verify the form is serving
curl -s http://<your-server-ip>:8080/ | grep '<form'
Confirmed: one <form> element with action /wp-json/demo/v1/submit.
Step 3: The Agent Scan - First Wall
stacker pipe scan --app wordpress-matomo
# Result: empty. No endpoints, no forms, no protocols detected.
The agent connected to the container but returned nothing. The container was running, the form was serving, but the agent couldn't find it.
The Problem: Container Name Mismatch
The agent resolves containers by name, not by the my.stacker.service label. Stacker generates the main app compose service with the hardcoded name app, producing containers named project-app-1. The agent's app code is wordpress-matomo but the container name doesn't match.
# Agent log shows the mismatch:
WARN No matching container found. Attempted patterns: exact match, prefix, suffix, contains
app_code="wordpress-matomo"
available_containers=["project-app-1", "project-matomo-1", ...]
The Fix: Rename the Compose Service
sed -i 's/^ app:/ wordpress-matomo:/' .stacker/docker-compose.yml
sed -i 's/my.stacker.service: "app"/my.stacker.service: "wordpress-matomo"/' .stacker/docker-compose.yml
After redeploy, the agent resolves correctly:
INFO Container name resolved via compose service label
app_code="wordpress-matomo" resolved_name="project-wordpress-matomo-1"
Step 4: Network Isolation - Second Wall
Even with the container name resolved, the probe returned empty. The agent could see the container but couldn't reach it.
# Check agent networks
docker inspect statuspanel_agent | jq '.[].NetworkSettings.Networks | keys'
# Result: agent on "default_network", project on "project_app-network"
Docker bridge networks are isolated by default. Containers on different networks can't communicate directly. The agent resolves the name but can't open a TCP connection.
# Fix: bridge the networks
docker network connect project_app-network statuspanel_agent
After this, the scan returns real data:
{
"forms": [{
"action": "/contact#wpcf7-f5-p6-o1",
"fields": ["your-name", "your-email", "your-subject", "your-message"]
}]
}
Step 5: Probe Scope - Third Wall
Even with the network connected, the remote_app probe scope (used by stacker pipe create) returns empty. The direct_container scope (used by --container) finds everything.
- remote_app (
--app <CODE>) - probes the app's published host port. Returns empty. - direct_container (
--app <CODE> --container <NAME>) - connects directly to the container's internal IP. Finds forms and endpoints.
# Use direct_container scope for reliable discovery
stacker pipe scan --app wordpress-matomo --container project-wordpress-matomo-1 --protocols html_forms
Key Findings
Container Matching
The agent matches app codes to container names using string patterns (exact/prefix/suffix/contains). Renaming the compose service from app to the project name fixes the match, but this is a workaround. The root fix belongs in Stacker's compose generator.
Network Injection
The status-panel agent runs on an external network. Project containers run on a compose-scoped bridge. They can't communicate unless manually bridged. Stacker already has inject_external_network() for NPM proxy mode - it needs to be enabled for status-panel mode too.
Probe Scope Gap
remote_app and direct_container probes behave differently. The pipe create wizard uses remote_app which doesn't find forms that direct_container does.
Commands Cheat Sheet
# Check agent sees containers
stacker agent status
# Scan with direct container probe (works)
stacker pipe scan --app <APP> --container <NAME> --protocols html_forms
# Check agent logs on server
docker logs statuspanel_agent | grep -i 'probe\|match\|resolve'
# Check which networks the agent is on
docker inspect statuspanel_agent --format '{{range $k,$_ := .NetworkSettings.Networks}}{{$k}} {{end}}'
# Bridge the networks
docker network connect project_app-network statuspanel_agent
# Rename compose service (workaround)
sed -i 's/^ app:/ <project-name>:/' .stacker/docker-compose.yml
sed -i 's/my.stacker.service: "app"/my.stacker.service: "<project-name>"/' .stacker/docker-compose.yml
Frequently Asked Questions
Why does stacker pipe scan return empty results?
Three common causes: (1) container name mismatch - rename the compose service to match the app code, (2) Docker network isolation - connect the agent to the project network, (3) wrong probe scope - use --container flag for direct container probing.
Do I need to bridge Docker networks every time?
The docker network connect command is ephemeral - it's lost on agent restart. For a persistent fix, add default_network as an external network in your compose file.
Can I use this pattern for other WordPress integrations?
Yes. The same approach works for any WordPress form plugin - Contact Form 7, WPForms, Gravity Forms. The pipe captures form submissions and delivers them to any HTTP endpoint.
What's the permanent fix for container name matching?
Stacker needs to inject the project name into the compose service name during generation, instead of hardcoding app. This is a one-line change in Stacker's compose generator.
Key Takeaways
- WordPress + Matomo deploy in one command with Stacker
- Three workarounds needed: compose service rename, network bridging, direct container probe
- Docker network isolation is the most common blocker for cross-container pipes
- The
--containerflag forces direct container probing which finds forms reliably - The PIPE architecture is sound - the gaps are in configuration, not code