Rules: DO NOT LIE, if you don't test it but claim it's working, that's lying. If you don't have creds for ssh or playwright, just ask don't waste time. If I don't give you the ip address or domain, don't try to wrongly login into a device that's not yours.
A Docker container management GUI plugin for RaspAP. Provides container, image, volume, and Compose project management through the RaspAP web interface.
# No local build step — PHP is interpreted, JS is vanilla
# Lint PHP
composer lint # requires composer install first
composer phpcs # PSR-2 code style check
# Release a new version
./release.sh v1.0.1 # bumps manifest.json, commits, tags, pushesDocker.php → Plugin entry point (implements PluginInterface)
DockerService.php → Docker CLI wrapper (containers, images, volumes, compose, daemon)
DockerJobManager.php → Background job tracking (pull, compose up/down)
DockerHubClient.php → Docker Hub v2 search API client
DockerUpdateService.php → Self-update via git tags
ajax/ → AJAX endpoints (one per action type)
docker_action.php → Container start/stop/rm, image delete, daemon start, inspect
docker_compose_action.php → Compose up/down/pull/restart
docker_create_container.php → Container creation with full params
docker_hub_search.php → Docker Hub search
docker_image_pull.php → Background image pull via DockerJobManager
docker_job_status.php → Poll background job status
docker_logs.php → Container log retrieval with timeout
docker_update_check.php → Plugin update check via GitHub Tags API
docker_update_apply.php → Plugin self-update execution
docker_volume_browse.php → Volume filesystem browser
app/js/Docker.js → All frontend logic (jQuery, polling engine, tab handlers)
templates/
main.php → Tab container layout + script/CSS loading
tabs/ → One template per tab (status, containers, images, compose, volumes, about)
config/
docker_plugin_update.sh → Self-update shell script (deployed to /etc/raspap/docker/)
RaspAP has a two-location deployment for plugins that you MUST understand:
- Plugin directory:
/var/www/html/plugins/Docker/— the git repo, source of truth - Webroot JS copy:
/var/www/html/app/js/plugins/Docker.js— a STALE copy made by PluginInstaller at install time, NEVER updated automatically
| File type | Served from | Updated by git checkout? | Needs extra step? |
|---|---|---|---|
| PHP templates | Plugin directory (directly) | Yes | Restart lighttpd (clear PHP opcache) |
| AJAX endpoints | Plugin directory (directly) | Yes | Restart lighttpd (clear PHP opcache) |
| JavaScript | Depends on script tag | Only if loaded from plugin dir | See below |
| Static assets (CSS, images) | Plugin directory (directly) | Yes | Browser cache-bust |
lighttpd's 50-raspap-router.conf rewrites all URLs through PHP except a whitelist: dist|app|ajax|config|plugins. The plugins entry is added by our update script because the upstream default does NOT include it. Without it, AJAX calls to plugins/Docker/ajax/*.php return the HTML login page instead of JSON.
The update script patches this automatically:
sed -i 's#dist|app|ajax|config#dist|app|ajax|config|plugins#' 50-raspap-router.confThe plugin template renders BEFORE the upstream layout appends jQuery/Bootstrap. Without defer, Docker.js loads first and $ is not defined:
Script #0: app/js/plugins/Docker.js ← jQuery doesn't exist yet
Script #1: dist/jquery/jquery.min.js ← too late
Solution: <script defer src="app/js/plugins/Docker.js"> ensures execution after document parsing completes.
The JS is also copied to app/js/plugins/Docker.js by the update script to keep the webroot copy in sync.
PHP opcache caches compiled PHP files in memory. After git checkout changes files on disk, PHP still serves the old cached versions. Templates, AJAX endpoints, and service classes are ALL affected.
Solution: The update script MUST restart lighttpd after checkout:
systemctl restart lighttpdWithout this, template changes (like adding type="button" to fix form submission) will NOT take effect even though the files on disk are correct.
- JS calls
docker_update_check.php→ PHP queries GitHub Tags API → returns latest version - If update available, user clicks "Update" → JS calls
docker_update_apply.php - PHP runs
docker_plugin_update.shvia sudo - Shell script:
git fetch --tags --force→git checkout vX.Y.Z→ patch lighttpd whitelist → copy JS to webroot → restart lighttpd - User reloads page → new PHP templates and JS are active
- Plugin interface: Implements
RaspAP\Plugins\PluginInterfacefrom upstream webgui - Namespace:
RaspAP\Plugins\Docker - All Docker CLI calls use
sudo /usr/bin/dockerwithescapeshellarg()— sudoers whitelist inmanifest.json - Background jobs: Long operations (image pull, compose up) run async via
DockerJobManager, polled from JS - AJAX auth: Every handler includes
autoload.php,CSRF.php,session.php,config.php,authenticate.phpfrom upstream - AJAX paths: Relative
../../../includes/(plugin installs at/var/www/html/plugins/Docker/) - Data persistence: Serialized to
/tmp/plugin__Docker.data(cleared on reboot) - Config path:
RASPI_DOCKER_CONFIG=/etc/raspap/docker
- JS copy trap: PluginInstaller copies JS once at install. Updates via git do NOT update the copy. Load JS from plugin dir, not the copy. See "Deployment Model" above.
- PHP opcache:
git checkoutdoes not clear opcache. MUST restart lighttpd after updating files. replace_allon utility functions: If you do a bulk find-replace (e.g. replacingJSON.parse(data)with a wrapper), check that you didn't also replace the call INSIDE the wrapper itself, creating infinite recursion.
- All templates are inside a
<form>in main.php (line 27). Every<button>MUST havetype="button"or it will default totype="submit"and submit the form instead of running its JS handler. This causes HTML5 validation errors on hiddenrequiredinputs (e.g. Create Container modal). - Bootstrap JS is v5.3.3 (loaded from
bootstrap.bundle.min.js). The non-minified JS files in the dist folder are stale v4.3.1 leftovers — ignore them. - Bootstrap CSS is v5.3.3. Use
data-bs-toggle,data-bs-dismiss(BS5 syntax), notdata-toggle(BS4). - Modals: Use
bootstrap.Modal.getOrCreateInstance(el).show()— NEVERnew bootstrap.Modal(el)which throws on second open because an instance already exists.
- jQuery may auto-parse JSON responses even without
Content-Type: application/json. All AJAX callbacks must handle both string and objectdata. Use thedockerParseJSON(data)utility. - Never set Content-Type
application/jsonon AJAX endpoints — causes a jQuery auto-parse bug. - Button colors in dark mode: Upstream
dark.cssappliesopacity: 75%to all.btn. Outline button variants (btn-outline-*) become unreadable. Use solid variants (btn-secondary,btn-danger, etc.) for action buttons.
- Plugin runs as
www-data— all Docker/git/systemctl commands need sudoers entries inmanifest.json manifest.jsonis both plugin metadata AND version source for self-update- Keep the Docker object in
plugins/manifest.json(the registry) in sync with pluginmanifest.json - No composer.json — PHP deps come from upstream webgui's autoloader
These rules exist because v1.0.2 through v1.0.5 all shipped broken. Every one of them could have been caught before pushing.
Reading source files is not verification. Grep counts and line numbers do not prove code works. Before claiming any fix:
- Run JavaScript through node to confirm it doesn't crash (e.g. test
dockerParseJSONwith both string and object input) - Run
php -lon changed PHP files to catch syntax errors - Trace the full execution path: button click → event handler → AJAX call → PHP endpoint → response → callback. If any step is untested, the fix is unverified.
- Never connect to network devices without explicit confirmation of the IP/hostname from the user.
Before fixing a broken button, look at a working button. Before writing CSS overrides, check what the readable buttons use. Before building a utility function, check if the existing pattern is simpler. Match what already works.
replace_all is dangerous. After any bulk find-replace:
- Read the changed file and verify every occurrence, especially any occurrence inside a function that IS the replacement
- Run the code (node, php -l, or browser) to confirm it doesn't crash
Do not stack multiple fixes into one release without verifying each one. If fix A is broken, fixes B and C shipped on top of it are also broken. Verify A works before adding B.
Before pushing any tag, be able to answer: "How does this change get from the git repo to the user's browser?" If the answer involves a copy step, a cache, or a restart, the fix isn't done until those steps happen.
- PSR-2 (enforced via phpcs in dev/psr2 branch)
- PHP 8.2+ with typed properties and return types
- Vanilla JavaScript (jQuery 3.5+, no build step)
- Bootstrap 5.3.3 for UI components