Interactive exec is available from client
3.2.0. The existing blocking
execStart() and wait() APIs are unchanged.poll() calls, even if the command is silent.
Non-TTY stdin and output
The defaultDocker::create() socket client supports this API. For explicit PHP
configuration, use DockerClientFactory::createInteractive():
my-container with a running development container. timeout must exist
inside its image; use the image’s own timeout/supervision mechanism if it does not.
writeStdin() returns the number of bytes accepted, at most 16 KiB per call.
Zero means backpressure: poll output, then retry the unsent suffix. The library
does not retain or queue stdin. Once all input has been sent, closeStdin() sends
EOF without closing the non-TTY output channels.
poll(50) reads at most 16 KiB and waits at most 50 milliseconds for data. It
returns true while the connection remains open, including during idle periods;
false means clean output EOF. Callbacks receive chunks, which can split frames
or lines. Without a callback, that channel is drained and discarded.
If stdin is already complete, wait($tick, 50) is a convenience loop. $tick
runs between polls even with no output; it can also send input. Callbacks and
ticks must return promptly. Their exceptions close the local session and propagate.
Deadlines and completion
The factory’stimeout controls connection and socket I/O timeouts for synchronous
HTTP setup/status calls. It is not a total request deadline: a response that keeps
arriving can extend a request, and application heartbeats cannot run during it.
The third argument to execStartInteractive() is a total streaming deadline in milliseconds,
starting after the HTTP upgrade. It is not reset when output arrives. Expiry throws
Docker\Exception\InteractiveExecTimeoutException and closes local I/O.
Worker locks and retries
Your application owns its queue, lock leases and execution records. Docker PHP provides the polling and stdin APIs; it does not acquire Redis locks, acknowledge jobs or deduplicate executions. Acquire the lease and persist the application execution ID and Docker exec ID before starting the command. Renew the lease between polls, atomically comparing its unique owner token so an expired worker cannot renew another worker’s lock. Use the ten-second heartbeat hook above even when the command is silent. Choose a lease duration with enough margin for synchronous setup/status requests and delayed heartbeats; a ten-second schedule is not a guarantee of exact timing. If renewal fails, stop sending input and close the local session. A heartbeat exception passed throughwait($tick) does this automatically. Record the failed
or uncertain attempt before acknowledging its job, and retain the Docker exec ID
for later inspection. A failed worker result does not prove the command stopped
or that it had no side effects.
Use persisted execution state to deduplicate repeated job deliveries. Before
starting another attempt after a lost lease or disconnect, confirm the previous
command has stopped and reconcile any side effects. A new queue delivery or an
expired lock alone does not make another exec safe.
Transports and TTY
This feature supports the bundled Unix, TCP and TLS socket transport. Guzzle and arbitrary PSR-18 clients are rejected before sending an interactive start request. HTTP proxies must preserve the HTTP 101 connection upgrade and bidirectional traffic. TTY sessions support stdin writes and combined output ononStdout(). They have
terminal semantics, not separate stdout/stderr pipes. closeStdin() deliberately
rejects TTY sessions: Docker can close their output when socket stdin reaches EOF.
Use terminal input or let the command exit; use Tty=false for JSON-input managers.
The Tty setting must match at exec creation and start.
Maintainer validation
On 6 October 2026, a private application canary tested client revisionfbdb2cc
with API package 7.1.45.0 against Docker Engine 29.8.1, using API 1.45.
Its worker sent fixed JSON stdin, closed stdin and polled a command that produced
no output for 40 seconds.
The locks, result callbacks and replay guards in these checks belonged to the
application. Concurrent workers, HTTP submission retries and network-failure
recovery still need separate tests.
The library’s stream and local transport-fixture tests cover partial stdin
writes, backpressure, EOF, idle ticks, deadline expiry, callback failures and
Unix/TCP/TLS connections. Run them without a Docker daemon:
tests/Resource/InteractiveExecResourceTest.php also exercises stdin, TTY output,
exit codes and container-side timeouts against a development Docker daemon.
Those resource tests create and remove their own containers.