Runs the SimpleX Chat client in headless bot mode, exposed over a WebSocket
1.1K
Runs the SimpleX Chat terminal client in headless bot mode, exposed over a WebSocket so other services (or your own scripts) can send and receive SimpleX messages programmatically.
SimpleX Chat is the first messenger with no user identifiers — not even random numbers. It's fully open source, end-to-end encrypted, and metadata-resistant by design.
simplex-chat in bot mode (-p 5226 --create-bot-display-name "SimpleX Bot" --create-bot-allow-files) so the binary auto-creates a fresh profile on first start.websocat, so any WebSocket client can drive the bot using the SimpleX bot protocol./data volume.The WebSocket has no built-in auth — anything that can reach the container can drive the bot.
External WebSocket client
│
│
└─▶ websocat (container :5225) ──▶ ws://127.0.0.1:5226
│
simplex-chat (-p 5226)
│
└─ /data (volume, HOME)
└─ .simplex/ (profile DB + keys)
├─ files/ (inbound, --files-folder)
├─ tmp/ (--temp-folder)
└─ outbound/ (consumer-written, to send)
This container exposes a single WebSocket interface on port 5225. Connect any WebSocket client to that URL.
The bot doesn't accept raw command strings over the WebSocket; every command must be wrapped in a small JSON envelope:
{ "corrId": "any-id-you-pick", "cmd": "/help" }
corrId is a correlation id you choose. The bot echoes it back in the
matching response so you can pair requests and replies when multiple are in
flight. Any unique string works.cmd is the SimpleX terminal command, exactly as you'd type it into the
simplex-chat CLI (leading slash included).Each reply is a JSON object containing the same corrId plus a resp field
with the command's result. The bot may also push unsolicited event messages
(without your corrId) — incoming chats, contact updates, etc.
See the upstream
SimpleX bots — sending commands
guide for the full protocol, and the
CLI command reference
for the list of cmd values you can send.
/user — get the active user./_connect 1 — create a one-time invitation link for user ID 1./contacts — list peers who have connected./profile <name> — change the bot's display name.Wrapped, those look like:
{ "corrId": "1", "cmd": "/user" }
{ "corrId": "2", "cmd": "/_connect 1" }
{ "corrId": "3", "cmd": "/contacts" }
{ "corrId": "4", "cmd": "/profile <name>" }
A response for the first command will look like this:
{
"corrId": "1",
"resp": {
"type": "activeUser",
"user": {
"userId": 1,
"profile": {
"displayName": "SimpleX Bot"
}
}
}
}
The bot's profile, configuration, and chat history all live in the /data volume.
Received files live under the bot's profile dir on the /data volume, as
siblings of the profile DB:
| Volume subpath | Purpose | simplex-chat flag |
|---|---|---|
.simplex/files | Files received by the bot (inbound) | --files-folder |
.simplex/tmp | In-progress transfers | --temp-folder |
files and tmp stay co-located under .simplex/ on one filesystem — required
because simplex-chat finishes a download with an atomic tmp → files rename
that fails across mounts.
A consumer (another service, or the openclaw-simplex plugin) exchanges files with the bot. The two directions work differently:
.simplex/files dir. So the two sides only need to share that one host
directory; the mountpoint can differ on each side (no matching path required).--outbound-folder flag; sending is not a simplex-chat option at all. On
send you pass a file path that the runtime resolves inside this container,
so the file must already be readable here. The simplest arrangement: a consumer
that shares this container's /data volume writes the file under
.simplex/outbound and passes the container path /data/.simplex/outbound/….
The bot won't fetch bytes over the WebSocket.Security: a consumer should share only what it needs — the .simplex/files
subpath for inbound and .simplex/outbound for sending — never .simplex/
itself or the whole /data volume, which hold the profile database and keys.
The container is configured through environment variables (all optional):
| Variable | Default | Purpose |
|---|---|---|
PROFILE_DISPLAY_NAME | SimpleX Bot | Profile display name. Applied only on first boot, when the profile is created; afterwards it lives on the persisted profile and is changed via the API. |
PROFILE_PEER_TYPE | bot | bot creates the profile as a SimpleX bot (peerType: "bot") so peers' apps highlight commands and show command menus. human creates a plain SimpleX user (pure transport / scripted node). The marker is cosmetic; file sharing works either way. Applies only on first boot. |
SMP_SERVERS | (presets) | Space-separated list of SMP relay addresses (smp://<fingerprint>@host) to use instead of simplex-chat's public presets. Unset = presets. |
XFTP_SERVERS | (presets) | Space-separated list of XFTP relay addresses for file transfer. Unset = presets. |
SIMPLEX_INBOUND_DIR | $HOME/.simplex/files | Received-files dir (--files-folder). |
SIMPLEX_TMP_DIR | $HOME/.simplex/tmp | In-progress-transfers dir (--temp-folder). |
WS_MAX_MESSAGE_BYTES | 16777216 (16 MiB) | Max WebSocket message size for the websocat bridge (-B). Above websocat's 64 KiB default so large SimpleX events/previews aren't split into partial frames (which corrupts the JSON the client parses). |
INBOUND_RETENTION_HOURS | (unset — never delete) | If set to a positive integer, a background janitor hourly deletes received files under SIMPLEX_INBOUND_DIR older than this many hours. simplex-chat keeps received files forever and a consumer's inbound mount is usually read-only, so this lets the runtime (which owns the dir) reclaim space. tmp is left alone. |
(There is no outbound dir variable — sending a file is not a simplex-chat setting; see File exchange.)
SMP_SERVERS / XFTP_SERVERS accept multiple space-separated addresses, e.g.
SMP_SERVERS="smp://[email protected] smp://[email protected]"; they're
passed to simplex-chat as a single --server / --xftp-server argument.
The SIMPLEX_* paths are advanced knobs and default under $HOME/.simplex
(which is /data/.simplex in this image, next to the profile DB). If you change
them, keep inbound and tmp on the same filesystem (simplex-chat finishes
a download with an atomic tmp → inbound rename that fails across mounts).
humanmode note: there's no CLI flag to create a non-bot profile headlessly, so on first boothumanmode answers simplex-chat's interactive display-name prompt over stdin. This is verified to work onsimplex-chat v6.5.5; if a future version changes the first-run prompt,human-mode profile creation may need revisiting.
docker run -d --name simplex-websocket-bridge \
-p 5225:5225/tcp \
-v /path/to/simplex-volume:/data \
--restart unless-stopped \
lundog/simplex-websocket-bridge:latest
/data (the container HOME) holds the bot's profile and chat history in
.simplex/, plus the inbound/tmp/outbound file dirs under .simplex/ — one
mount, one filesystem, so simplex-chat's atomic tmp → files rename works.
Sending files needs a consumer that shares this volume (or its .simplex/outbound
subpath); see File exchange. The WebSocket control interface is
reachable at ws://localhost:5225.
This image bundles third-party programs that run as separate
processes and keep their own licenses, so the image as distributed is an
aggregate — SPDX-License-Identifier: MIT AND AGPL-3.0-only:
Dockerfile). Because it is AGPL, the image is distributed under AGPL-3.0
terms with respect to that component (including the network-use provision,
AGPL §13); the corresponding source is the upstream repository at that tag.Content type
Image
Digest
sha256:570ee4a91…
Size
52.1 MB
Last updated
about 2 months ago
docker pull lundog/simplex-websocket-bridge