WHMCS + Docker: hands off container actions
You know the drill. A client pings you because their service is “down,” and suddenly you’re SSH’d into a server, typing the same Docker commands you typed yesterday, hoping you don’t hit the wrong container this time.
Hosting business owners feel it when tickets spike. Ops leads feel it during maintenance windows. Even a solo WHMCS admin ends up babysitting restarts, log pulls, and “quick checks.” This WHMCS Docker automation turns those repeat tasks into a reliable API you can trigger from WHMCS.
This workflow gives your WHMCS module a predictable back end in n8n, runs predefined Docker actions over SSH, then returns clean results to WHMCS. You’ll see what it does, what you need, and where teams usually trip up.
How This Automation Works
The full n8n workflow, from trigger to final output:
n8n Workflow Template: WHMCS + Docker: hands off container actions
flowchart LR
subgraph sg0["Flow 1"]
direction LR
n0@{ icon: "mdi:swap-horizontal", form: "rounded", label: "If", pos: "b", h: 48 }
n1@{ icon: "mdi:swap-vertical", form: "rounded", label: "Parametrs", pos: "b", h: 48 }
n2["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/webhook.dark.svg' width='40' height='40' /></div><br/>API"]
n3["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/webhook.dark.svg' width='40' height='40' /></div><br/>422-Invalid server domain"]
n4["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/code.svg' width='40' height='40' /></div><br/>Code1"]
n5@{ icon: "mdi:cog", form: "rounded", label: "SSH", pos: "b", h: 48 }
n6@{ icon: "mdi:swap-horizontal", form: "rounded", label: "Container Actions", pos: "b", h: 48 }
n7@{ icon: "mdi:swap-horizontal", form: "rounded", label: "Service Actions", pos: "b", h: 48 }
n8["<div style='background:#f5f5f5;padding:10px;border-radius:8px;display:inline-block;border:1px solid #e0e0e0'><img src='https://flowpast.com/wp-content/uploads/n8n-workflow-icons/webhook.dark.svg' width='40' height='40' /></div><br/>API answer"]
n9@{ icon: "mdi:swap-vertical", form: "rounded", label: "Inspect", pos: "b", h: 48 }
n10@{ icon: "mdi:swap-vertical", form: "rounded", label: "Stat", pos: "b", h: 48 }
n11@{ icon: "mdi:swap-vertical", form: "rounded", label: "Start", pos: "b", h: 48 }
n12@{ icon: "mdi:swap-vertical", form: "rounded", label: "Stop", pos: "b", h: 48 }
n13@{ icon: "mdi:swap-vertical", form: "rounded", label: "Test Connection1", pos: "b", h: 48 }
n14@{ icon: "mdi:swap-vertical", form: "rounded", label: "Deploy", pos: "b", h: 48 }
n15@{ icon: "mdi:swap-vertical", form: "rounded", label: "Suspend", pos: "b", h: 48 }
n16@{ icon: "mdi:swap-vertical", form: "rounded", label: "Terminated", pos: "b", h: 48 }
n17@{ icon: "mdi:swap-vertical", form: "rounded", label: "Unsuspend", pos: "b", h: 48 }
n18@{ icon: "mdi:swap-vertical", form: "rounded", label: "Mount Disk", pos: "b", h: 48 }
n19@{ icon: "mdi:swap-vertical", form: "rounded", label: "Unmount Disk", pos: "b", h: 48 }
n20@{ icon: "mdi:swap-vertical", form: "rounded", label: "Log", pos: "b", h: 48 }
n21@{ icon: "mdi:swap-vertical", form: "rounded", label: "ChangePackage", pos: "b", h: 48 }
n22@{ icon: "mdi:swap-vertical", form: "rounded", label: "Deploy-docker-compose", pos: "b", h: 48 }
n23@{ icon: "mdi:swap-vertical", form: "rounded", label: "Version", pos: "b", h: 48 }
n24@{ icon: "mdi:swap-vertical", form: "rounded", label: "Users", pos: "b", h: 48 }
n25@{ icon: "mdi:swap-horizontal", form: "rounded", label: "If1", pos: "b", h: 48 }
n26@{ icon: "mdi:swap-horizontal", form: "rounded", label: "MinIO", pos: "b", h: 48 }
n27@{ icon: "mdi:swap-vertical", form: "rounded", label: "nginx", pos: "b", h: 48 }
n28@{ icon: "mdi:swap-horizontal", form: "rounded", label: "Container Stat", pos: "b", h: 48 }
n29@{ icon: "mdi:swap-vertical", form: "rounded", label: "GET ACL", pos: "b", h: 48 }
n30@{ icon: "mdi:swap-vertical", form: "rounded", label: "SET ACL", pos: "b", h: 48 }
n31@{ icon: "mdi:swap-vertical", form: "rounded", label: "GET NET", pos: "b", h: 48 }
n0 --> n28
n0 --> n6
n0 --> n26
n0 --> n25
n0 --> n3
n2 --> n1
n25 --> n27
n25 --> n7
n20 --> n5
n5 --> n4
n10 --> n5
n12 --> n5
n4 --> n8
n26 --> n23
n26 --> n24
n11 --> n5
n24 --> n5
n27 --> n22
n14 --> n5
n29 --> n5
n31 --> n5
n9 --> n5
n30 --> n5
n15 --> n5
n23 --> n5
n1 --> n0
n17 --> n5
n18 --> n5
n16 --> n5
n19 --> n5
n21 --> n5
n28 --> n9
n28 --> n10
n28 --> n20
n7 --> n13
n7 --> n14
n7 --> n15
n7 --> n17
n7 --> n16
n7 --> n21
n13 --> n5
n6 --> n11
n6 --> n12
n6 --> n18
n6 --> n19
n6 --> n29
n6 --> n30
n6 --> n31
n22 --> n7
end
%% Styling
classDef trigger fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
classDef ai fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
classDef aiModel fill:#e8eaf6,stroke:#3f51b5,stroke-width:2px
classDef decision fill:#fff8e1,stroke:#f9a825,stroke-width:2px
classDef database fill:#fce4ec,stroke:#c2185b,stroke-width:2px
classDef api fill:#fff3e0,stroke:#e65100,stroke-width:2px
classDef code fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
classDef disabled stroke-dasharray: 5 5,opacity: 0.5
class n0,n6,n7,n25,n26,n28 decision
class n2,n3,n8 api
class n4 code
classDef customIcon fill:none,stroke:none
class n2,n3,n4,n8 customIcon
The Problem: Manual Docker “Fixes” Don’t Scale in WHMCS
WHMCS is great at selling and managing services, but it’s not the place you want humans doing infrastructure work from memory. The painful part isn’t a single restart or log fetch. It’s the pile-up. One client needs a restart, another needs stats, another needs a mount fixed, and now you’re juggling container names, paths, and “which server was this on?” context switching. A small slip (wrong container, wrong host, wrong command) becomes downtime, data loss, or a messy ticket thread that never really closes.
It adds up fast, especially once you have more than a handful of active services.
- Every “quick SSH check” steals 10 minutes, then pulls you into 30 more.
- Different techs run slightly different commands, which means inconsistent outcomes and harder troubleshooting later.
- Logs and stats get copied into tickets manually, so details are missed or pasted from the wrong service.
- Provisioning changes (plan changes, suspends, resumes) become risky because they’re done under pressure.
The Solution: WHMCS Triggers, n8n Executes, Docker Responds
This workflow acts like an API “control room” between WHMCS and your Docker server. WHMCS (or a companion module like the Docker MinIO WHMCS module) sends a command to a secured n8n webhook. n8n validates the request, checks that the request is meant for the right server domain, and routes the command to the correct action. Then it connects to the Docker host via SSH and runs a predefined Bash script for that task. Finally, it formats the output into a consistent response (often JSON) and returns it to WHMCS so the module can display status, logs, or results without guesswork.
The workflow starts at an incoming webhook with Basic Auth. From there it sets base parameters (like server domain and directory paths), gates the action type, and uses routers to decide which script to run. The SSH runner executes the script, then a response formatter standardizes what WHMCS receives.
What You Get: Automation vs. Results
| What This Workflow Automates | Results You’ll Get |
|---|---|
|
|
Example: What This Looks Like
Say your team handles 20 “container actions” a week from WHMCS tickets: restarts, fetching logs, checking stats, mounting a volume, that kind of thing. Manually, even a careful tech might spend about 10 minutes per action between SSH, finding the right container, running commands, and pasting results back, so that’s roughly 3 hours a week. With this workflow, the action is triggered from WHMCS in about a minute, the SSH execution happens in the background, and the response comes back formatted. You still review edge cases, but the busywork mostly disappears.
What You’ll Need
- n8n instance (try n8n Cloud free)
- Self-hosting option if you prefer (Hostinger works well)
- WHMCS to trigger actions from client/service events
- Docker host where containers and compose live
- SSH credentials (create on the Docker server user)
Skill level: Intermediate. You’ll be comfortable creating credentials in n8n and adjusting a few server-specific parameters (domain, directories, and scripts).
Don’t want to set this up yourself? Talk to an automation expert (free 15-minute consultation).
How It Works
A secured webhook receives the command. WHMCS calls the “Incoming Webhook Endpoint” with Basic Auth. That request includes the action type (like start, stop, logs, stats, provision, suspend) and the service context.
Requests get validated and normalized. The workflow sets base parameters first, then checks that the server domain matches what you expect. If it doesn’t, it returns a clear 422-style response instead of doing something dangerous on the wrong host.
The correct script is selected and executed. Routers split traffic into container commands (start/stop/mount/unmount/logs/stats/ACL/network stats) and service commands (provision, suspend, resume, terminate, change plan). n8n then generates the relevant Bash script in a dedicated node and runs it through the SSH runner against the Docker server.
Results are formatted for WHMCS. The workflow turns raw command output into a predictable response, then replies to the original webhook call. WHMCS gets consistent logs, stats, inspect output, or confirmation messages that you can store, display, or attach to tickets.
You can easily modify the Docker Compose template and nginx configuration to match your product. See the full implementation guide below for customization options.
Step-by-Step Implementation Guide
Step 1: Configure the Webhook Trigger
This workflow starts with a secured webhook that receives container management commands.
- Add the Incoming Webhook Endpoint node and set Path to
docker-minio. - Set Response Mode to
responseNodeand enable Multiple Methods. - Set Authentication to
basicAuth. - Credential Required: Connect your
httpBasicAuthcredentials to Incoming Webhook Endpoint.
command, domain, username, password, disk, ram, and cpu. Missing fields will break downstream scripts.Step 2: Set Base Parameters and Validate Domain
Base parameters are centralized and used across all container scripts, and a domain validation gate protects the API.
- In Base Parameter Set, set server_domain to your identifier (currently
[YOUR_ID]), clients_dir to/opt/docker/clients, and mount_dir to/mnt. - Keep screen_left as
{{and screen_right as}}for Docker stats templating. - In Domain Match Check, confirm the comparison uses
{{ $json.server_domain }}equals{{ $('Incoming Webhook Endpoint').item.json.body.server_domain }}. - Route invalid domains to 422 Domain Error Reply so the API returns the configured JSON error payload.
[YOUR_ID] in Base Parameter Set to your actual server domain ID before testing.Step 3: Configure Parallel Routing for Commands
After validation, the workflow fans out into multiple routing branches to handle container info, container actions, MinIO actions, and service actions.
- Ensure Domain Match Check outputs to Container Info Switch, Container Command Router, MinIO Command Switch, and Action Type Gate in parallel.
- In Container Info Switch, confirm routes for
container_information_inspect,container_information_stats, andcontainer_logare based on{{ $('Incoming Webhook Endpoint').item.json.body.command }}. - In Container Command Router, verify outputs for
container_start,container_stop,container_mount_disk,container_unmount_disk,container_get_acl,container_set_acl, andcontainer_get_net. - In MinIO Command Switch, ensure
app_versionroutes to App Version Script andapp_usersroutes to List Users Script. - In Action Type Gate, confirm the
create,change_package, andunsuspendconditions route to Nginx Config Builder, and other commands route to Service Command Router.
Domain Match Check outputs to both Container Info Switch and Container Command Router and MinIO Command Switch and Action Type Gate in parallel.
Step 4: Build Templates and Command Scripts
This workflow builds Docker Compose and Nginx templates, then creates shell scripts used by the SSH executor. There are 20+ set nodes; configure them by function rather than individually.
- In Nginx Config Builder, keep the prefilled values for main, main_location, console, and console_location (these are injected into scripts later).
- In Compose Template Builder, verify the Docker Compose template uses expressions like
{{ $('Incoming Webhook Endpoint').item.json.body.domain }}and{{ $('Base Parameter Set').item.json.mount_dir }}. - Ensure Compose Template Builder flows into Service Command Router to support create, suspend, resume, terminate, and plan change operations.
- Review the script-building set nodes that feed Remote SSH Runner: Start Container Script, Stop Container Script, Provision Container Script, Suspend Service Script, Resume Service Script, Terminate Service Script, Change Plan Script, Mount Volume Script, Unmount Volume Script, Retrieve ACL Script, Apply ACL Script, Network Stats Script, Compose Inspect Script, Container Stats Script, Fetch Logs Script, App Version Script, and List Users Script.
{{ $('Base Parameter Set').item.json.clients_dir }} and {{ $('Base Parameter Set').item.json.mount_dir }}. If those paths are wrong, all SSH actions will fail.Step 5: Configure Remote Execution and Responses
All command scripts are executed over SSH and normalized into a standard API response.
- In Remote SSH Runner, set Command to
{{ $json.sh }}and CWD to/. - Credential Required: Connect your
sshPasswordcredentials to Remote SSH Runner. - Ensure all script nodes route into Remote SSH Runner, which then flows to Format Response Script.
- Keep Format Response Script in runOnceForEachItem mode to wrap outputs into
{status, message, data}JSON. - Return the final response through Webhook Success Reply with Respond With set to
allIncomingItems.
Step 6: Test & Activate Your Workflow
Verify the webhook, routing, and SSH execution before enabling production use.
- Click Execute Workflow and send a test POST request to Incoming Webhook Endpoint with a valid JSON body and correct
server_domain. - Confirm that Domain Match Check routes to the correct branch and that one of the script nodes sends a command to Remote SSH Runner.
- Verify the response is normalized by Format Response Script and returned by Webhook Success Reply with a
statusofsuccess. - When tests succeed, toggle the workflow to Active to accept production webhook calls.
Common Gotchas
- Webhook Basic Auth credentials can expire or get rotated. If calls suddenly fail, check the n8n credential used by the Incoming Webhook Endpoint first, then confirm WHMCS is sending the updated value.
- If you’re using Wait-style timing elsewhere or the Docker host is under load, SSH execution time can vary. When downstream nodes return empty output, the fix is often to increase timeouts on the SSH command and avoid assuming “instant” responses.
- SSH permissions are usually the real culprit when scripts fail. Make sure the SSH user can run Docker commands (often by being in the docker group) and can read/write the directories you set for clients_dir and mount_dir.
Frequently Asked Questions
Plan on about 1 hour if your SSH and WHMCS details are ready.
No. You’ll mostly be configuring credentials and editing a few parameters and scripts that are already laid out for you.
Yes. n8n has a free self-hosted option and a free trial on n8n Cloud. Cloud plans start at $20/month for higher volume. You’ll also need to factor in your server costs (and any paid WHMCS module licensing you use).
Two options: n8n Cloud (managed, easiest setup) or self-hosting on a VPS. For self-hosting, Hostinger VPS is affordable and handles n8n well. Self-hosting gives you unlimited executions but requires basic server management.
Yes, but you’ll want to change the Compose Template Builder content and the Provision Container Script so they generate the right docker compose configuration for your app. Many teams also adjust the Nginx Config Builder so the proxy matches their domain pattern and headers. If you need different actions, you can add a new route in the Container Command Router or Service Command Router, then connect it to a new “script” node that feeds the SSH runner.
Usually it’s Basic Auth not matching what WHMCS is sending. Check the webhook credentials in n8n, then confirm the module’s endpoint URL and auth values. The next most common issue is the Domain Match Check blocking requests because server_domain doesn’t match the domain in the payload.
If you self-host n8n, there’s no hard execution limit; it mainly depends on your VPS size and how busy the Docker host is.
For Docker work, n8n is usually the better fit because it’s comfortable with multi-branch routing, SSH execution, and structured responses, all in one workflow. Zapier and Make can trigger webhooks, sure, but SSH-style infrastructure actions tend to feel bolted on. Another factor is control: self-hosting n8n keeps your operations inside your environment, which matters for hosting providers. Cost can be simpler too, because you’re not paying extra for every branch or every “advanced” step. If you’re torn, Talk to an automation expert and map it to your ticket volume and risk tolerance.
Once this is in place, Docker actions stop being a “who can SSH right now?” problem. The workflow handles the repetitive work, and your team gets back to higher-value support and operations.
Need Help Setting This Up?
Our automation experts can build and customize this workflow for your specific needs. Free 15-minute consultation—no commitment required.