Combining PhpStorm, Xdebug and Logs
Anyone debugging Docker containers with nothing but var_dump and logs is leaving most of the development environment's potential on the table. Xdebug 3 with PhpStorm connects the debugger directly to the container, for real breakpoints, variable inspection and stack traces without a single var_dump().
Table of Contents
- 1. Why Xdebug needs different configuration in container setups
- 2. Configuring Xdebug 3 in the PHP-FPM container
- 3. Setting up the PhpStorm server and path mappings
- 4. Setting breakpoints and starting a debug session
- 5. Variables, watch expressions and evaluate
- 6. Reading stack traces and the call stack in PhpStorm
- 7. Combining container logs and the debugger
- 8. CLI debugging: debugging Magento commands
- 9. var_dump vs. Xdebug: a direct comparison
- 10. Summary
- 11. FAQ
1. Why Xdebug needs different configuration in container setups
In a traditional development environment where PHP runs directly on the host, Xdebug simply connects to the IDE via localhost. In Docker containers this is fundamentally different: the container is an isolated network environment, and localhost inside the container is not the host. Xdebug needs to know which IP address the IDE (PhpStorm) is reachable at, and that address differs depending on the operating system and Docker configuration.
On Linux systems with Docker, the host is typically reachable via the gateway IP of the Docker network, often 172.17.0.1 or the IP of the docker0 interface. On macOS and Windows, Docker Desktop provides the special DNS name host.docker.internal, which automatically resolves to the host IP. Mark Shust's Docker Magento setup uses a combination of network configuration and environment variables on Linux to solve this problem, the file compose.dev-linux.yaml contains the matching Xdebug configuration.
Xdebug 3 has a completely reworked configuration schema compared to Xdebug 2. The old xdebug.remote_* directives have been replaced by the new xdebug.client_* schema. Anyone who copies configuration from the Xdebug 2 era will wonder why no connection is established, the old directives are silently ignored in Xdebug 3. Switching to xdebug.client_host and xdebug.client_port is the most common fix for "Xdebug won't connect" problems.
2. Configuring Xdebug 3 in the PHP-FPM container
Xdebug configuration for PHP-FPM containers happens via an INI file that is either built into the image or mounted as a volume. For Mark Shust's Docker Magento setup, this file lives at src/dev/php/xdebug.ini and is loaded when the container starts. The most important directives for Xdebug 3: xdebug.mode set to debug (or develop,debug for extended development helpers), xdebug.client_host set to the host IP, xdebug.client_port set to 9003 (the Xdebug 3 default), and xdebug.start_with_request=yes for automatic debug start, or trigger for on demand debugging.
For production or environments with performance requirements, xdebug.mode=off is recommended. Xdebug disabled with mode=off effectively has no performance impact, because the extension is loaded but performs no profiling or debugging. In Magento 2 the difference is measurable: a page that loads in 800ms with xdebug.mode=debug and active tracing loads noticeably faster with mode=off. For the development setup, start_with_request=trigger is therefore recommended, so Xdebug only becomes active for requests carrying the special cookie or query parameter.
The environment variable PHP_IDE_CONFIG with the value serverName=projectname is an often forgotten but critical piece of configuration. This variable tells Xdebug which server name to report in its communication with the IDE. PhpStorm uses this server name to find the path mappings, without a matching server name the breakpoints will not work, even though the connection appears to be established.
; src/dev/php/xdebug.ini: Xdebug 3 for Docker + PhpStorm
; For Xdebug 2: use xdebug.remote_* directives instead
[xdebug]
; Load Xdebug (add this line to php.ini or zend_extension= in xdebug.ini)
; zend_extension=xdebug
; Mode: debug for step debugging, develop for var_dump enhancement
xdebug.mode = debug
; Start debug session with XDEBUG_SESSION cookie or query param
; Use 'yes' to start with every request (slower, not recommended)
xdebug.start_with_request = trigger
; IDE host, host.docker.internal resolves on macOS/Windows
; On Linux: use gateway IP (172.17.0.1) or set via compose env
xdebug.client_host = host.docker.internal
xdebug.client_port = 9003
; Server name, must match PhpStorm Server configuration name
; Set via docker-compose environment: PHP_IDE_CONFIG=serverName=mironsoft
xdebug.idekey = PHPSTORM
; Increase timeout for slow Magento requests
xdebug.connect_timeout_ms = 2000
; Show full variable values (useful for large Magento arrays)
xdebug.var_display_max_depth = 5
xdebug.var_display_max_children = 256
xdebug.var_display_max_data = 1024
3. Setting up the PhpStorm server and path mappings
Under Settings → PHP → Servers, create a new server. The name must match exactly the value in PHP_IDE_CONFIG=serverName=NAME. Host is localhost (or the project's domain), port 80 (or the mapped Nginx port). The most important part: path mappings. Here you map the local path (e.g. /home/mir/development/mironsoft/src) to the path inside the container (e.g. /var/www/html).
Without correct path mappings, PhpStorm cannot find the files when Xdebug reports a breakpoint. In practice this means that on the first breakpoint PhpStorm shows a file from the container that cannot be matched to the local copy. PhpStorm then asks whether you want to map the file manually, that is the manual route. The clean route is to configure the path mappings completely up front, so PhpStorm opens the correct local file immediately on every breakpoint.
For Magento 2 with vendor packages, you additionally need to make sure the vendor path is mapped too. Breakpoints in Magento core files or third party modules only work if PhpStorm can map the container path /var/www/html/vendor/ to the local src/vendor/ path. Anyone who regularly debugs core code should set up the path mappings completely once and then export them as an XML configuration.
4. Setting breakpoints and starting a debug session
Breakpoints are set in PhpStorm by clicking the line number gutter on the left of the code. A red dot appears, and PhpStorm pauses execution when that line is reached. Debug listening must be active, either via the phone icon in the toolbar ("Start Listening for PHP Debug Connections") or via Run → Start Listening for PHP Debug Connections. PhpStorm then listens on port 9003 for incoming Xdebug connections.
The debug session is triggered when an HTTP request arrives at the server carrying the Xdebug trigger cookie (XDEBUG_SESSION=PHPSTORM) or the query parameter (?XDEBUG_SESSION_START=PHPSTORM). The browser plugin "Xdebug Helper" (available for Chrome and Firefox) sets this cookie with one click. Alternatively, you can create a run configuration of type "PHP Remote Debug" in PhpStorm that triggers a prepared browser request with the right cookie.
When PhpStorm receives the connection and finds the breakpoint, execution pauses and the debugger window opens automatically. You see the current execution point in the code, the variables in the current scope, the call stack, and can navigate through the code with the navigation buttons (Step Over, Step Into, Step Out, Resume). Conditional breakpoints allow pausing only under certain conditions, for example only when $productId === 42, so you do not stop on every loop iteration.
5. Variables, watch expressions and evaluate
The variables panel in the debugger window shows all variables in the current scope: local variables of the function, $this with all properties of the object, and global variables. For Magento 2, which relies heavily on dependency injection and complex object graphs, this overview is particularly valuable. You not only see the variable value, you can also navigate into objects, clicking an object expands its properties, recursively down to the configured depth.
Watch expressions let you observe specific expressions across multiple breakpoints. You enter a PHP expression, for example $this->config->getValue('general/store_information/name'), and PhpStorm evaluates that expression every time execution pauses in the debugger. This lets you track values that are computed in one function but used in another, without having to manually look them up at every step.
The Evaluate feature (keyboard shortcut Alt+F8) lets you run arbitrary PHP expressions in the current context. You can call methods, set values, test conditions, directly at runtime, without changing the code. In Magento 2 you can use this, for example, to check whether a repository call returns the expected data, or to query the state of the cache, without interrupting the debugging session.
<?php
// Example: Debugging a Magento 2 ViewModel with PhpStorm Xdebug
// Set breakpoint on the line below to inspect all injected dependencies
declare(strict_types=1);
namespace Mironsoft\Catalog\ViewModel;
use Magento\Framework\View\Element\Block\ArgumentInterface;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Store\Model\StoreManagerInterface;
/**
* Product detail view model, demonstrates Xdebug inspection points.
*/
class ProductDetail implements ArgumentInterface
{
public function __construct(
private readonly ProductRepositoryInterface $productRepository,
private readonly StoreManagerInterface $storeManager,
) {
// Breakpoint here: inspect $this to verify injected dependencies
}
/**
* Get product by SKU, set breakpoint to inspect $product object graph.
*/
public function getProductBySku(string $sku): ?\Magento\Catalog\Api\Data\ProductInterface
{
try {
// Breakpoint here: Watch Expression '$sku' and $this->storeManager->getStore()->getId()
$product = $this->productRepository->get($sku);
return $product; // Inspect full product object with all extension attributes
} catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
// Breakpoint here: check $e->getMessage() in Evaluate window
return null;
}
}
}
6. Reading stack traces and the call stack in PhpStorm
The call stack in the debugger window shows the complete call chain that led to the current line of code. In Magento 2 this stack can easily be 30 or more levels deep, because the event observer mechanism, plugin interception and the dependency injection framework each add their own stack frames. PhpStorm shows every frame with file name, line number and method name. Clicking a frame jumps to that point in the code and shows the variables of that frame.
Particularly valuable when debugging Magento 2 plugins: when a plugin (interceptor) is called, the call stack shows both the original code and the generated interceptor code under generated/code/. PhpStorm needs to know this path in the path mappings too, so you can set breakpoints in the generated files. This is relevant when you want to understand the order in which before, around and after plugins are called.
When PhpStorm stops on a line that does not open the expected file, it is often a path mapping problem with generated files. The fix: under Settings → PHP → Servers, check whether /var/www/html/generated is mapped to /home/mir/development/mironsoft/src/generated. After a setup:di:compile, the contents of the generated/ folder change, PhpStorm can only index the new files correctly after reloading the changed files or restarting the project.
7. Combining container logs and the debugger
The debugger alone only shows what happens in the code. Container logs show what happens in the infrastructure: which SQL queries the database executes, which Nginx requests arrive, which Redis operations are performed. Combining a breakpoint with an open log tab in the Services tool window gives a complete picture: exactly when does the debugger pause, and which log entries were produced at that moment?
For Magento 2, var/log/exception.log and var/log/debug.log are particularly relevant. Instead of streaming these logs in the terminal, you can open them in the PhpStorm log tab (Run → Open Log or via the Services tool window). With an open debugger and an open log tab side by side, you immediately see when an exception is logged before it reaches the breakpoint, or when an event observer produces a side effect that is not directly visible in the debugger.
An advanced combination: enable Xdebug tracing (xdebug.mode=debug,trace) and combine the trace with the log tab. The trace file shows every function call with a timestamp and execution time, which lets you locate performance problems that are not visible during normal debugging. PhpStorm can open trace files directly and display them in a structured view where you can filter for expensive function calls.
8. CLI debugging: debugging Magento commands
Besides web requests, CLI commands can also be debugged via Xdebug. For Magento commands such as bin/magento cache:flush, indexer:reindex or custom console commands, this is particularly useful. The configuration differs slightly: instead of a browser cookie, the environment variable XDEBUG_SESSION=PHPSTORM is set, and xdebug.start_with_request=yes or =trigger with the environment variable set activates the debugger for the CLI process.
In Mark Shust's setup, the script bin/debug-cli is already prepared, it sets this environment variable and starts the PHP CLI process with Xdebug. Instead of bin/magento cache:flush, you call bin/debug-cli bin/magento cache:flush, PhpStorm receives the connection and pauses at the first breakpoint in the command code. This is particularly valuable for debugging DataPatch, schema patch and setup upgrade issues, which are not accessible from a web browser.
A common problem with CLI debugging: the Xdebug timeout is too short for slow Magento commands such as setup:di:compile. The directive xdebug.connect_timeout_ms = 5000 gives enough time for the initial connection to be established. In addition, you should make sure max_execution_time = 0 is set in the PHP CLI context, so long running commands are not interrupted by a timeout while you are waiting at a breakpoint.
#!/bin/bash
# bin/debug-cli: Enable Xdebug for CLI commands in Docker container
# Usage: bin/debug-cli bin/magento indexer:reindex catalogsearch_fulltext
# Enable Xdebug for this single CLI execution
export XDEBUG_SESSION=PHPSTORM
export XDEBUG_CONFIG="client_host=host.docker.internal client_port=9003"
# Run the command inside the PHP container with Xdebug environment
docker compose exec \
-e XDEBUG_SESSION=PHPSTORM \
-e XDEBUG_CONFIG="client_host=host.docker.internal client_port=9003" \
-e PHP_IDE_CONFIG="serverName=mironsoft" \
php-fpm \
php "$@"
# PhpStorm must be listening for debug connections (Run → Start Listening)
# Set breakpoints in the command class before running this script
# Example: src/app/code/Mironsoft/Catalog/Console/Command/SyncProducts.php
9. var_dump vs. Xdebug: a direct comparison
The choice between var_dump() and Xdebug is not a matter of style, it is a matter of efficiency. Both have their place, but for complex debugging scenarios, such as tracing a Magento 2 plugin stack or investigating a dependency injection error, the debugger is clearly more powerful.
| Aspect | var_dump / Logging | Xdebug + PhpStorm | When to use which? |
|---|---|---|---|
| Setup effort | No setup needed | One time configuration | var_dump for a quick check |
| Variable depth | Limited, hard to read | Complete, navigable | Xdebug for complex objects |
| Call stack | debug_backtrace() only | Complete, clickable | Xdebug for plugin stacks |
| Performance impact | Minimal | Noticeable (mode=debug) | var_dump in perf tests |
| Code change needed | Yes (must be removed) | No | Xdebug for clean code |
In practice: Xdebug is ideal for the structured debugging of bugs during the development phase, where you need to understand the program flow. var_dump and logging are useful for quick value checks during development and for debugging in environments where Xdebug is not available. The goal is to never leave var_dump calls in a commit, PhpStorm warns about this with an inspection and can remove them automatically via a quick fix.
Mironsoft
Magento 2 debugging and development infrastructure with Docker and PhpStorm
Want Xdebug set up in your Docker container?
We configure Xdebug 3 for your Docker Magento setup, set up PhpStorm with correct path mappings, and train your team in professional debugging without var_dump guesswork.
Xdebug setup
Configure Xdebug 3 in Docker, set up PHP_IDE_CONFIG and path mappings
PhpStorm config
Server configuration, run configs and debug workflows for the whole team
Debugging training
Hands on teaching of breakpoints, watch expressions and CLI debugging in Magento 2
10. Summary
Xdebug 3 in Docker container setups is no black magic, but it does require correctly configuring four places: the xdebug.ini in the container, the PHP_IDE_CONFIG environment variable, the PhpStorm server configuration with path mappings, and debug listening in the IDE. Once these four places are configured correctly, debugging works reliably, with real breakpoints, complete variable inspection, a clickable call stack and an Evaluate function for arbitrary PHP expressions.
Combining the debugger with container logs in the Services tool window gives the complete picture: what happens in the code (debugger), and what happens in the infrastructure (logs). CLI debugging via the XDEBUG_SESSION environment variable makes Magento console commands debuggable too. The result is a debugging environment that replaces var_dump guesswork with structured analysis and reduces debugging time from hours to minutes.
PhpStorm + Xdebug in Docker, the essentials at a glance
Xdebug 3 configuration
xdebug.mode=debug, client_host=host.docker.internal, client_port=9003, start_with_request=trigger. Set PHP_IDE_CONFIG=serverName=projectname.
Path mappings
Settings → PHP → Servers: map local path /src/ to /var/www/html/. Also map generated/ for plugin interceptor debugging.
Starting a debug session
Xdebug Helper browser plugin sets the XDEBUG_SESSION cookie. PhpStorm must be set to Run → Start Listening for PHP Debug Connections.
CLI debugging
Set XDEBUG_SESSION=PHPSTORM as an env variable. Use the bin/debug-cli script for docker compose exec with Xdebug variables.