Self-hosting Sophie

Last updated: September 13, 2026

This guide explains how to self-host Sophie Bot using Podman and Ansible, mirroring the official production environment.

Architecture Overview

Sophie is designed to run as a set of microservices to ensure scalability and high availability:
  • Stable Instance: The primary bot instance that handles most user interactions. It can also act as a proxy to the Beta instance.
  • Beta Instance: A secondary instance used for testing new features.
  • Scheduler: Handles background tasks, timed events, and scheduled operations.
  • REST API: Provides an interface for the web dashboard and external integrations.
All components are containerized and typically run using Podman.

Prerequisites

Before starting, ensure you have the following installed on your host system:
  • Podman: For container management.
  • Ansible: For automated deployment.
  • MongoDB: Persistent data storage.
  • Redis / Valkey: For caching and FSM (Finite State Machine) storage.

Deployment with Ansible

The recommended way to deploy Sophie is using the provided Ansible playbooks in the
deploy/
directory.

1. Configuration

Copy
data/config.example.env
to
data/config.env
and fill in the required values. Key variables include:
  • TOKEN
    : Your Telegram Bot API token.
  • MONGO_HOST
    : Connection string for MongoDB.
  • REDIS_HOST
    : Hostname for Redis.
  • OPENROUTER_API_KEY
    ,
    MISTRAL_API_KEY
    ,
    OPENAI_API_KEY
    : Seed the AI catalog on first migration, and are never read again. See below.

2. Run the Playbook

To deploy the stable environment:
Loading...
To deploy the beta environment (includes scheduler and REST API):
Loading...

Telegram Mini App authentication

Mini App login at
/auth/login/tma
validates Telegram init data with the bot token using
init-data-py
v1. Init data has an explicit one-day lifetime; older credentials are rejected, so clients must reopen the Mini App to obtain fresh init data rather than reuse it indefinitely. Malformed init data (including missing required fields) or validated data without a user returns HTTP 400. Invalid hashes and expired credentials return HTTP 401. A valid Telegram user not registered in Sophie's database still receives HTTP 403. The successful token response is unchanged.

Greetings and welcome security

greetings_ephemeral
sends the welcome only to the members it greets, one message each, filled with their own name. Nothing is posted to the chat, so the clean-welcome cleanup has nothing to delete afterwards.
welcomecaptcha_ephemeral
sends the captcha prompt as an ephemeral message to each new member instead of posting it in the chat. Only they see it, one prompt per member rather than one for the batch, and nothing is left behind to clean up — so the prompt is not deleted when the captcha is passed, because there is nothing there to delete.
A prompt whose security note is an album is still posted to the chat:
sendMediaGroup
cannot address one member, and splitting the album into separate ephemeral messages is no way around it — Telegram accepts at most five ephemeral messages per user.

AI models and providers

Sophie's AI models, the endpoints they are served from and the API keys used to reach them live in the database, not in the configuration file. They are managed at runtime with operator commands, so adding a model or rotating a key never needs a redeploy.

Seeding

The
seed_ai_catalog
migration creates the initial catalog from your environment:
  • OPENROUTER_API_KEY
    becomes the
    openrouter
    provider.
  • MISTRAL_API_KEY
    and
    OPENAI_API_KEY
    become the
    mistral
    and
    openai
    providers, of kind
    moderation
    . These carry no models: they hold the key for a service Sophie calls with the vendor's own SDK — the moderation classifiers, and Mistral's voice and video transcription.
  • CUSTOM_PROVIDERS
    becomes one provider per entry, for OpenAI-compatible endpoints:
Loading...
Every one of these variables is read only by a seed migration. Once the catalog exists, changing them has no effect — use the commands below instead.

Managing the catalog

Command

Purpose

/op_aiprovidersList providers. API keys are always masked.
/op_aiprovider <name> ^kind= ^base_url= ^key= ^enabled=Create or update a provider. Private chat only; the command message is deleted immediately.
/op_aimodelsList models and what each one is used for.
/op_aimodel <name> ^provider= ^api_name= ^role= ^unrole= ^reasoning= ^enabled=Create or update a model.
kind
is
openrouter
,
openai_compatible
, or
moderation
(a key for a vendor SDK rather than a chat-completions endpoint — do not point models at one). A role is
<mode>:<purpose>
— for example
^role=support:chatbot
— or just
<purpose>
for the purposes that are not per-chat (
summary
,
moderation_reason
). Purposes are
chatbot
,
translation
,
filters
,
summary
and
moderation_reason
,
sophie_inspect
; modes are
entertainment
,
moderation
,
support
,
sophie_pm
and
sophie_help
.
A mode with no model for a purpose falls back to the
support
tier, so you only need to define the roles you want to differ. Changes take effect on every process within a few seconds without a restart.

AI moderation

The AI moderator classifies messages against nine categories and deletes anything that crosses a threshold. It does not go through the AI catalog above — it calls a dedicated moderation classifier, chosen with the
ai_moderation_provider
feature flag:

Value

Model

Catalog provider holding the key

mistral (default)mistral-moderation-latestmistral
openaiomni-moderation-latestopenai
Switching backend is two steps: put the key in the catalog, then flip the flag. Neither needs a restart — the client is rebuilt as soon as the catalog version changes.
Loading...
The two providers report different categories, and Sophie normalises them onto its own nine.
health
,
financial
,
law
and
pii
have no OpenAI equivalent and never trigger on that backend.

Per-chat detection levels

Chat admins run
/aimoderator
to get a table of the nine categories and a button for each one. Pressing a button walks that category through Off → Low → Medium → High.
The level multiplies the classifier's score before it is compared to the threshold, so a chat can be made more or less sensitive without an operator retuning anything. The factors are feature flags:

Level

Flag

Default

Lowai_moderation_level_low_multiplier0.7
Mediumai_moderation_level_normal_multiplier1.0
Highai_moderation_level_high_multiplier1.3
Off skips the category entirely rather than scaling it to zero.

Tuning thresholds

Every threshold is a feature flag, so it can be changed per chat with no redeploy:
Loading...
Flags are named
ai_moderation_threshold_<provider>_<the provider's own category>
, because categories that Sophie groups together do not score alike:
sexual/minors
needs a much lower cut-off than
sexual
. These are the operator-level tuning; per-chat sensitivity is the detection level above.
The "message deleted" notice removes itself after
ai_moderation_notice_delete_after_seconds
(30 by default); set it to
0
to keep the notices in the chat.

Experimental: source inspection

ai_sophie_inspect
lets the Sophie-help assistant start a sub-agent that reads Sophie's own source code when the documentation cannot answer a question. It is off by default because it costs several model requests per question.
Groups do not get it from their AI mode.
ai_sophie_inspect_chats
is a space or comma separated list of group IDs allowed to use it anyway, for the chats where people ask how Sophie works.
Every run is bounded:
ai_sophie_inspect_request_limit
,
ai_sophie_inspect_tool_calls_limit
and
ai_sophie_inspect_output_tokens_limit
cap one run,
ai_sophie_inspect_daily_chat_limit
caps how many runs one chat may start per day, and the tokens are charged to that chat's AI quota like any other feature.
The model it uses is the catalog model holding the
sophie_inspect
role, so it is swapped like any other:
/op_aimodel <name> ^role=sophie_inspect
. Prefer a cheap one — the daily cap is what bounds the damage, not the price per run.
The sub-agent can only read
.py
files inside the
sophie_bot
package, and only through search and bounded reads — it never executes anything and never sees configuration or data.

Running with Podman (Manual)

If you prefer to run containers manually, you can use the following logic (based on the Quadlet templates):

Stable Instance

Loading...

Scheduler

The scheduler is the same image but runs with
MODE=scheduler
.
Loading...

REST API

The REST API is the same image but runs with
MODE=rest
.
Loading...

Proxy System (Stable + Beta)

Sophie implements a unique proxying system where the Stable instance can redirect traffic to the Beta instance. This allows seamless transitions and testing of new features.
  • PROXY_ENABLE
    : Set to
    True
    to enable proxying.
  • PROXY_STABLE_INSTANCE_URL
    : URL of the stable instance.
  • PROXY_BETA_INSTANCE_URL
    : URL of the beta instance.
When enabled, the bot can route requests between instances based on configuration, allowing for "canary" style deployments or easy beta testing for specific users/chats.

Environment Variables Reference

Variable

Description

TOKENTelegram Bot Token
MONGO_DBMongoDB Database Name
REDIS_DB_FSMRedis Database index for FSM
OWNER_IDTelegram User ID of the bot owner
ENVIRONMENTName of the environment (e.g., production-stable)
MODESet to scheduler for the scheduler service