A self-hosted Signal messaging bot that forwards your messages to an Ollama server and returns AI-powered responses. Built on signal-cli-rest-api and fully containerized with Docker.
Important
Attribution: This project relies on the excellent work of signal-cli-rest-api for Signal communication and Ollama for local LLM inference.
Tip
Recommended Setup: It is highly recommended to configure this bot with a separate mobile number dedicated to the bot's identity. This ensures your personal Signal account remains unaffected.
Send a message from Signal, get a response from your local LLM — privately, with no cloud APIs, no subscriptions, and no data leaving your network.
Note
While this application has been tested predominantly with the gemma4:latest model from Ollama, all models are expected to work correctly.
There are existing Signal bot frameworks (like Clawbot/OpenClaw) that abstract the Signal protocol, but this project intentionally uses a direct signal-cli-rest-api + Ollama architecture:
| Approach | This Project (Direct) | Clawbot/OpenClaw (Framework) |
|---|---|---|
| Privacy | ✅ No middleware; self-hosted; messages go from Signal → your code → local LLM | |
| Control | ✅ Full access to Signal CLI features; customize polling, retries, error handling | |
| LLM Flexibility | ✅ Native Ollama integration; any local model, no API keys | |
| Deployment | ✅ Fully containerized; runs entirely offline | |
| Setup Complexity | ❌ Manual Signal linking required; more initial configuration | ✅ Often simpler onboarding with hosted solutions |
| Maintenance | ❌ You own the integration; updates depend on signal-cli-rest-api | ✅ Framework handles protocol updates |
- 💬 Conversational AI — Chat with any Ollama model directly from Signal
- 🧠 Conversation memory — Per-user chat history with configurable depth
- 🔄 Model switching — Change models on the fly via slash commands
- 📊 Telemetry mode — View token counts, speed, and timing per response
- 🔒 Sender allowlist — Restrict access to specific phone numbers
- 🐳 Docker containerized — One-command deployment
- 📡 Auto-detection — Automatically finds available Ollama models at startup
Important
Privacy Disclaimer: This bot is designed for users who want complete data sovereignty. However, understanding exactly how your data moves is crucial for maintaining Signal's privacy guarantees. The containerisation and architecture used by user for OSB will affect privacy.
- Signal → signal-cli-rest-api: Messages travel via Signal's standard end-to-end encryption (E2EE). The REST API decrypts them locally using your linked device's keys. Your messages are never decrypted on any remote server.
- signal-cli-rest-api → Bot: Plaintext messages travel over localhost HTTP (Docker internal network only). This stays within your machine.
- Bot → Ollama: Prompts are sent via HTTP to your specified Ollama URL (typically
localhostor your LAN). No internet connection required if Ollama is local.
| Data | Location | Retention | Notes |
|---|---|---|---|
| Conversation History | In-memory (Python dict) | Until bot restart or /reset |
Never written to disk |
| Signal Keys | ./signald-config/ volume |
Persistent | Required for Signal protocol |
| Phone Numbers | .env allowlist, logs |
Persistent | For access control; logged |
| Message Content | In-memory | Until restart or /reset | Not written to disk by the bot; exists in conversation history |
- Prerequisites
- Signal Setup (Dedicated Number)
- Ollama Setup
- Installation
- Configuration
- Running the Bot
- Bot Commands
- Architecture
- Troubleshooting
- License
- Alternative Projects
| Requirement | Purpose |
|---|---|
| Docker & Docker Compose | Runs the bot and Signal API containers |
| Ollama | Local LLM inference server |
| Python 3.10+ | Runs the one-time Signal linking setup script |
| A dedicated Signal phone number | The bot's identity on Signal (see below) |
| A primary Signal account on your phone | Used to link the bot as a secondary device |
The bot needs its own phone number registered with Signal. This is a separate number from your personal Signal account. You will link the bot to this number as a secondary device (like Signal Desktop).
Your personal Signal number stays on your phone. The bot runs as a linked device on a second number. When someone messages the bot's number, it receives and responds automatically.
-
Get a second phone number:
- Use a secondary SIM card, eSIM, or a VoIP number that can receive SMS (for initial Signal registration only)
-
Install a second copy of Signal using App Cloner (Android):
- Install App Cloner on your Android device
- Open App Cloner and select Signal from your app list
- Tap Clone — this creates an independent second copy of Signal
- Open the cloned Signal app and register it with your second phone number
- Complete SMS verification as normal
-
Verify it works:
- Send a test message between your primary Signal and the cloned Signal to confirm both accounts are functional
Alternative (iOS / no App Cloner): Use a second device (old phone, tablet) with a separate number to install Signal. Or use a VoIP service that supports SMS verification.
- The bot's phone number must have Signal already registered and active
- The bot links as a secondary device — your cloned Signal app remains the primary
- The cloned app must stay installed (the bot is a linked device to it)
The bot connects to an Ollama server to generate responses. Ollama must be running and accessible from the machine where Docker is running.
Follow the instructions at ollama.com for your platform.
# Pull a model (e.g., Gemma 4)
ollama pull gemma4:latest
# Or any other model
ollama pull llama3.2:latest
ollama pull mistral:latestcurl http://localhost:11434/api/tagsYou should see a JSON response listing your downloaded models.
If Ollama and the bot run on the same machine, the default http://host.docker.internal:11434 works on Docker Desktop (Windows/macOS). On Linux, use your machine's LAN IP instead:
OLLAMA_URL=http://192.168.1.100:11434Tip: Ensure Ollama is bound to 0.0.0.0 (not just 127.0.0.1) if accessing from Docker. Set OLLAMA_HOST=0.0.0.0 in your Ollama environment.
-
Clone the repository
git clone https://github.com/your-username/ollama-signal-bot.git cd ollama-signal-bot -
Create your environment file
cp .env.example .env
-
Edit .env with your settings
# Your bot's Signal phone number (the cloned/second number) SIGNAL_PHONE_NUMBER=+1234567890 # Your Ollama server URL OLLAMA_URL=http://192.168.1.100:11434 # Model to use (leave empty for auto-detection) OLLAMA_MODEL=gemma4:latest # Custom system prompt SYSTEM_PROMPT=You are a helpful assistant that answers questions concisely. # Access control (* = allow all, or comma-separated phone numbers) ALLOWED_SENDERS=*
-
Install Python dependencies (for the setup script)
pip install httpx python-dotenv
| Variable | Required | Default | Description |
|---|---|---|---|
SIGNAL_PHONE_NUMBER |
Yes | — | The bot's Signal phone number (with country code, e.g. +1234567890) |
OLLAMA_URL |
No | http://host.docker.internal:11434 |
URL of your Ollama server |
OLLAMA_MODEL |
No | (auto-detect) | Ollama model name (e.g. gemma4:latest). If empty or invalid, the bot picks the first available model. |
SYSTEM_PROMPT |
No | You are a helpful assistant. | System prompt sent with every conversation |
ALLOWED_SENDERS |
No | * |
Comma-separated phone numbers, or * to allow everyone |
Control who can message the bot:
# Allow everyone (default)
ALLOWED_SENDERS=*
# Single user
ALLOWED_SENDERS=+15551234567
# Multiple users
ALLOWED_SENDERS=+15551234567,+15559876543Unauthorized senders receive a denial message and are logged.
Before running the Signal API, ensure you have created your .env file (see Configuration above).
docker-compose build
docker-compose up -d signal-apiWait a few seconds for the Signal API to initialize.
You can run the setup script either locally or inside the Docker container (after Step 3).
Option A: Local execution (Recommended)
python signal-setup.pyOption B: Docker execution
docker-compose run --rm ollama-signal-bot python signal-setup.pyThis interactive script will:
- Connect to the Signal API
- Generate a QR code URL
- Prompt you to scan it from the cloned Signal app on your phone
To scan the QR code:
- Open the QR code URL in your browser
- On the cloned Signal app → Settings → Linked Devices → tap +
- Scan the QR code displayed in your browser
- Press Enter in the setup script to confirm
docker-compose up -ddocker-compose logs -f ollama-signal-botYou should see:
Bot is running — polling for messages... (model: gemma4:latest)
Windows (PowerShell):
.\run.ps1Linux/macOS:
chmod +x run.sh
./run.shThese scripts handle building, starting, account detection, and log tailing automatically.
Send any of these as a message to the bot's Signal number:
| Command | Description |
|---|---|
/help |
Show all available commands |
/heartbeat |
Show bot uptime, service status, and stats |
/version |
Show Ollama server version |
/list |
List all available models with sizes |
/model |
Show the currently active model |
/model <name> |
Switch to a different model |
/ps |
List currently loaded/running models |
/show <name> |
Show detailed info about a model |
/reset |
Clear your conversation history |
/history |
Show your conversation statistics |
/maxhistory |
Show the current memory limit |
/maxhistory <n> |
Set max conversation history to n message pairs |
/verbose |
Toggle response telemetry (token counts, speed, timing) |
With /verbose enabled, each response includes:
─── Telemetry ───
Model: gemma4:latest
Input tokens: 142 (0.35s | 405.7 t/s)
Output tokens: 87 (1.24s | 70.2 t/s)
Model load: 0.02s
Total time: 1.61s
┌─────────────┐ ┌──────────────────┐ ┌────────────┐
│ Signal │ HTTP │ signal-cli- │ HTTP │ Ollama │
│ Phone │◄──────► │ rest-api │◄──────► │ Signal │
│ (User) │ │ (Docker) │ │ Bot │
└─────────────┘ └──────────────────┘ │ (Docker) │
└─────┬──────┘
│ HTTP
┌─────▼──────┐
│ Ollama │
│ Server │
└────────────┘
- signal-cli-rest-api — Bridges Signal's protocol to a REST API (port 18080 on host)
- ollama-signal-bot — The Python bot that polls for messages and queries Ollama
- Ollama — Your local LLM inference server (runs on the host or another machine)
The Signal REST API has a built-in Swagger UI for debugging:
http://localhost:18080/v1/about
- Bot can't reach Ollama
- Ensure
OLLAMA_URLin.envuses an IP accessible from inside Docker - On Linux, use your LAN IP —
host.docker.internalonly works on Docker Desktop - Check Ollama is bound to
0.0.0.0: setOLLAMA_HOST=0.0.0.0before starting Ollama
- Ensure
- "Receive failed (account not registered?)"
- Run
python signal-setup.pyand complete the linking process - Ensure the cloned Signal app is still installed and the linked device wasn't removed
- Run
- Bot responds with "No model is currently loaded"
- Pull a model:
ollama pull gemma4:latest - Or set a valid model in
.envand restart:docker-compose restart ollama-signal-bot
- Pull a model:
- Port 18080 already in use
- Change the host port in
docker-compose.yml:"19080:8080"(and updatesignal-setup.pyandrun.ps1/run.shaccordingly)
- Change the host port in
- Messages not being received
- Check logs:
docker-compose logs -f ollama-signal-bot - Verify the Signal API is healthy:
curl http://localhost:18080/v1/about - Ensure your
SIGNAL_PHONE_NUMBERmatches the linked account
- Check logs:
MIT License — see LICENSE for details.
If this project doesn't quite fit your needs, here are other excellent Signal + Ollama/LLM integrations worth exploring:
| Project | Description | Key Features |
|---|---|---|
| Signallama | Dedicated Signal bot for Ollama chat | Direct Signal-to-Ollama interface, focused simplicity |
| uoltz | Local-first AI chatbot for Signal | Pluggable skills, voice transcription, multi-agent orchestration |
| Signal-to-Ollama Bridge | Minimal Express.js bridge service | Emoji reactions, conversation context tracking, lightweight |
| Clawdbot/OpenClaw | Multi-platform bot framework | Supports Signal, WhatsApp, Telegram; Ollama local provider support |
| Signal Bot Crawly | Rust-based Signal ↔ Open WebUI bridge | Group chat support, summarization, web search integration |
Author: Rudi Ball
Built with Ollama, signal-cli-rest-api, and Python.
