Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 0 additions & 13 deletions docs/getting-started/docker.md

This file was deleted.

166 changes: 166 additions & 0 deletions docs/getting-started/docker.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
---
title: Docker
---

import ConsoleSample from '@site/src/components/ConsoleSample';

There are two ways to get PHP Debugger into a container: use a prebuilt image with
the debugger already compiled into the interpreter, or install the extension into
an official PHP image yourself.

The prebuilt images are the simpler route and the one to reach for unless you have
a reason not to.

## Prebuilt images

The images on Docker Hub are drop-in replacements for the
[official PHP images](https://hub.docker.com/_/php). Change one line:

```dockerfile
# before
FROM php:8.4-fpm

# after
FROM phpdebugger/php:8.4-fpm
```

That is the whole setup. There is no extension to install, nothing to enable, and
no separate Dockerfile for development.

### Available tags

Each tag matches the official `php:` tag of the same name, for PHP **8.2** to
**8.5**, on `linux/amd64` and `linux/arm64`:

| Tag | Distro |
| --- | --- |
| `8.x-cli` (also `8.x`) | Debian |
| `8.x-fpm` | Debian |
| `8.x-apache` | Debian |
| `8.x-zts` | Debian |
| `8.x-cli-alpine` (also `8.x-alpine`) | Alpine |
| `8.x-fpm-alpine` | Alpine |
| `8.x-zts-alpine` | Alpine |
| `latest` | newest stable PHP, cli variant |

There are no patch-level tags such as `8.4.23`. Each tag always carries the latest
patch release of its PHP minor version, rebuilt weekly and on every debugger
release.

### What is different from the official image

Everything you already do keeps working — same entrypoints, same helper scripts,
same config layout:

```dockerfile
FROM phpdebugger/php:8.4-fpm

RUN docker-php-ext-install -j$(nproc) pdo_mysql bcmath
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
```

Two things are not the same:

- The debugger is compiled into the interpreter as a static extension. It does not
appear in the `.ini` files, and it cannot be uninstalled.
- The JIT compiler is switched off in the bundled opcache build, because it is
incompatible with the debugger's engine hooks.

If you rebuild opcache yourself, JIT comes back unless you say otherwise:

```dockerfile
RUN docker-php-ext-configure opcache --disable-opcache-jit \
&& docker-php-ext-install -j$(nproc) opcache
```

### Connecting your IDE

No INI configuration is needed. Debugging is on by default, every request starts a
session, and the debugger connects whenever your IDE is listening — at near-zero
cost when it is not.

```yaml
services:
app:
image: phpdebugger/php:8.4-fpm
environment:
PHP_DEBUGGER_CONFIG: "client_host=host.docker.internal"
PHP_IDE_CONFIG: "serverName=myapp"
extra_hosts:
- "host.docker.internal:host-gateway" # needed on Linux
```

### Checking it worked

<ConsoleSample>{`$ docker run --rm phpdebugger/php:8.4-cli php -v
PHP 8.4.24 (cli) (built: Aug 3 2026 12:08:27) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.4.24, Copyright (c) Zend Technologies
with Zend OPcache v8.4.24, Copyright (c), by Zend Technologies
with PHP Debugger v__VERSION__, Copyright (c) 2002-2026, by Derick Rethans`}</ConsoleSample>

:::warning[Development images only]

Debugging is enabled by default in these images. A debugger gives anyone who can
reach it full access to your source, your variables, and your runtime data, so a
reachable production container is a serious risk. Keep production on the official
`php:` images and use these only where you actually want to debug.

:::

### Removing an existing debugger

If the image you are switching already had a debugger installed, take the old one
out. PHP Debugger presents the same interface for compatibility, so leaving the
previous extension in place means two extensions competing for the same engine
hooks.

From your Dockerfile and INI files, remove:

- the line that loads the old extension — `zend_extension=xdebug.so`, or a
`docker-php-ext-enable xdebug` step;
- any `xdebug.mode` setting — debugging is on by default here;
- any `xdebug.start_with_request` setting — every request already starts a session.

Settings worth keeping can stay as they are. Both the `xdebug.*` and
`php_debugger.*` prefixes are accepted, so an existing client host or port carries
over untouched.

## Installing the extension with PIE

If you would rather keep the official image and add the debugger to it, install
the extension with [PIE](https://github.com/php/pie), the PHP Foundation's
extension installer.

```dockerfile
FROM php:8.4-cli

RUN apt-get update \
&& apt-get install -y --no-install-recommends $PHPIZE_DEPS unzip libtool \
&& curl -fsSL https://github.com/php/pie/releases/latest/download/pie.phar \
-o /usr/local/bin/pie \
&& chmod +x /usr/local/bin/pie \
&& pie install php-debugger/php-debugger \
&& rm -rf /var/lib/apt/lists/*
```

PIE compiles the extension against the PHP in the image and enables it for you.
The defaults match the prebuilt images: debugging on, a session with every
request, port 9003.

Build the image, then check the debugger is in it:

<ConsoleSample>{`$ docker build -t myapp .
$ docker run --rm myapp php -v
...
with PHP Debugger v__VERSION__, Copyright (c) 2002-2026, by Derick Rethans`}</ConsoleSample>

The trade-off against the prebuilt images is build time and image size: you are
compiling a C extension and carrying the build toolchain, rather than pulling an
image that already has the debugger in it.

## Next steps

- [More install options](./install-options.md) — prebuilt binaries, package
managers, and building from source
- [Configuration](./configuration.md) — the settings you can change
2 changes: 1 addition & 1 deletion docs/getting-started/installation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ else.

:::tip[Prefer Docker?]

Use a container image instead — see [Docker](./docker.md).
Use a container image instead — see [Docker](./docker.mdx).

:::

Expand Down
70 changes: 67 additions & 3 deletions docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,43 @@

import {themes as prismThemes} from 'prism-react-renderer';

/* The released version is shown in the navbar and quoted in sample output on the
docs. Read it from the extension's latest GitHub release at build time so a
rebuild is all it takes to pick up a new release, rather than hunting down
every place a version is written down.

FALLBACK_VERSION keeps builds working when the API cannot be reached -- offline,
or rate limited, since this is an unauthenticated request. It is only a
backstop: bump it when it drifts too far from reality. */
const FALLBACK_VERSION = '0.3.0';

/* Stand-in written into the navbar config below and swapped for the real version
in createConfig, so the version lives in exactly one place. */
const VERSION_PLACEHOLDER = '__VERSION__';

async function latestDebuggerVersion() {
const url =
'https://api.github.com/repos/php-debugger/php-debugger/releases/latest';
try {
const response = await fetch(url, {
headers: {Accept: 'application/vnd.github+json'},
});
if (!response.ok) {
console.warn(
`[version] GitHub returned ${response.status}; falling back to ${FALLBACK_VERSION}`,
);
return FALLBACK_VERSION;
}
const {tag_name: tag} = await response.json();
return typeof tag === 'string' ? tag.replace(/^v/, '') : FALLBACK_VERSION;
} catch (error) {
console.warn(
`[version] could not reach GitHub (${error.message}); falling back to ${FALLBACK_VERSION}`,
);
return FALLBACK_VERSION;
}
}

/** @type {import('@docusaurus/types').Config} */
const config = {
title: 'PHP Debugger',
Expand Down Expand Up @@ -107,11 +144,11 @@ const config = {
},
{
type: 'dropdown',
label: 'v1.3.0',
label: VERSION_PLACEHOLDER,
position: 'right',
items: [
{
label: 'v1.3.0 (latest)',
label: `${VERSION_PLACEHOLDER} (latest)`,
href: 'https://github.com/php-debugger/php-debugger/releases/latest',
},
{
Expand All @@ -130,4 +167,31 @@ const config = {
}),
};

export default config;
export default async function createConfig() {
const debuggerVersion = await latestDebuggerVersion();
const navbar = config.themeConfig.navbar;
return {
...config,
/* Available to pages via useDocusaurusContext().siteConfig.customFields. */
customFields: {...config.customFields, debuggerVersion},
themeConfig: {
...config.themeConfig,
navbar: {
...navbar,
items: navbar.items.map((item) =>
item.type === 'dropdown' && item.label === VERSION_PLACEHOLDER
? {
...item,
label: `v${debuggerVersion}`,
items: item.items.map((sub) =>
sub.label === `${VERSION_PLACEHOLDER} (latest)`
? {...sub, label: `v${debuggerVersion} (latest)`}
: sub,
),
}
: item,
),
},
},
};
}
15 changes: 15 additions & 0 deletions src/components/ConsoleSample/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import CodeBlock from '@theme/CodeBlock';
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';

/* Console output with __VERSION__ swapped for the release the docs were built
against. Sample output that quotes a version goes stale the moment a new one
ships; this way a rebuild is enough to correct it. The version itself is
resolved in docusaurus.config.js. */
export default function ConsoleSample({children}) {
const {siteConfig} = useDocusaurusContext();
const text = String(children).replace(
/__VERSION__/g,
siteConfig.customFields.debuggerVersion,
);
return <CodeBlock language="console">{text}</CodeBlock>;
}
Loading