Lightweight soft-PLC designed for educational use, simulations, and small-scale automation projects.
195
A soft PLC in one small executable. One container = one PLC.
Write your control program in IEC 61131-3 Structured Text. SimplePLC compiles and checks it before it runs, then runs it on a fixed cycle. The process image is served over Modbus TCP and OPC UA, with a web panel, a REST API and an MCP server that AI agents can use to set up and program the PLC.
scratch) image, about 11 MBlinux/amd64, linux/arm64 and linux/arm/v7 (32-bit Raspberry Pi OS)docker run -d --name plc1 \
-p 8080:8080 -p 502:502 -p 4840:4840 \
-v plc1:/data \
tommyhbe/simpleplc:2.0.0-dev
Then:
docker logs plc1SimplePLC 2.0.0
Data /data
Web panel port 8080 in the container (e.g. http://localhost:8080 with -p 8080:8080)
Setup code f06495c7-fe822152 (enter it in the web panel to create your account)
PLC c122354f2cde Modbus :502 OPC UA :4840 [RUN]
Ready. Press Ctrl+C to stop.
| Port | Service |
|---|---|
8080 | Web panel, REST API (/api/v1) and MCP endpoint (/mcp) |
502 | Modbus TCP server (any unit id, addresses from 0) |
4840 | OPC UA server, opc.tcp://<host>:4840, tags as ns=1;s=<tag> |
| Path | Contents |
|---|---|
/data | Configuration, programs and their history, retentive memory |
The server runs as an unprivileged user (65534).
These are read when a new /data is first created.
| Variable | Default | Description |
|---|---|---|
SIMPLEPLC_ID | the container's hostname | The PLC's name |
SIMPLEPLC_EXAMPLE | none | Start from an example: tank, traffic-light, remote-io, blink, factory-io-conveyor |
SIMPLEPLC_TOKEN | – | Administrator API token for scripts and agents (never stored) |
claude mcp add --transport http simpleplc http://localhost:8080/mcp \
--header "Authorization: Bearer <token>"
Create a token under Users and tokens in the panel, or pass one with SIMPLEPLC_TOKEN.
Several PLCs are several containers that talk Modbus TCP. A link in a PLC's configuration makes it read and write another device's registers. The programs themselves only use tags. A fleet gives one panel and one MCP endpoint for all of them:
export SIMPLEPLC_TOKEN=$(openssl rand -hex 16)
docker network create plant
docker run -d --name tank --hostname tank --network plant -e SIMPLEPLC_TOKEN \
-e SIMPLEPLC_EXAMPLE=tank tommyhbe/simpleplc:2.0.0-dev
docker run -d --name alarms --hostname alarms --network plant -e SIMPLEPLC_TOKEN \
-e SIMPLEPLC_EXAMPLE=remote-io tommyhbe/simpleplc:2.0.0-dev
docker run -d --name fleet --network plant -p 127.0.0.1:8080:8080 -e SIMPLEPLC_TOKEN \
-e SIMPLEPLC_DEVICES=tank,alarms tommyhbe/simpleplc:2.0.0-dev fleet
alarms reads tank's level every 200 ms over Modbus TCP and raises an alarm above a limit.
Open http://localhost:8080 to see both PLCs on one panel.
Warning
**Modbus TCP has no authentication, and OPC UA runs with security policy None.** Anyone who can reach those ports can read and write the PLC. The panel and API need a login or token, but use plain HTTP.Publish ports only on networks you trust, for example
-p 127.0.0.1:502:502.
Programs have no file, process or network access. They read and write their tags, and the bytecode is verified before it runs.
Content type
Image
Digest
sha256:e24258cdd…
Size
3.1 MB
Last updated
about 8 hours ago
docker pull tommyhbe/simpleplc