Official Codex CLI in Docker, controlled from Telegram.
This project runs the official @openai/codex CLI inside Docker and exposes it through a Telegram bot.
The bridge only:
- receives Telegram messages
- runs
codex exec - sends the result back to Telegram
It supports:
- per-chat login with official device auth
- persistent chat sessions with
/new - image input from Telegram photos and image documents
- audio input from Telegram voice notes and audio files
- audio replies for voice-driven chats
- language-aware voice defaults through
VOICE_LOCALE - live progress updates in a single Telegram message while Codex works
- host workspace access
- full host shell access when
HOST_SHELL_MODE=host - host Docker access
- automatic Codex updates
- Create a Telegram bot with BotFather.
- Get your Telegram
chat_id. - Copy the config template:
cd /home/ubuntu/docker/codex-telegram
cp .env.example .env- Fill the minimum config:
TELEGRAM_BOT_TOKEN=your_bot_token
TELEGRAM_ALLOWED_CHAT_IDS=your_chat_id
CODEX_AUTH_MODE=per_chat
CODEX_AUTH_ROOT=/data/auth
HOST_WORKSPACE=/home/ubuntu
HOST_SHELL_MODE=host
CODEX_CHANNEL=latest
VOICE_LOCALE=en_US- Start it:
docker compose up -d --build- In Telegram, run:
/login
Complete the OpenAI device-auth flow in your browser.
- Send a task:
list all running docker containers on this machine
| Command | Purpose |
|---|---|
| any plain message | Run the prompt through Codex |
| voice note or audio file | Transcribe the audio and run it as the prompt |
| photo or image document | Send the image to Codex, using the caption as the prompt when present |
/run <prompt> |
Run a task explicitly |
/login |
Start official Codex login for this chat |
/login status |
Show login state |
/login cancel |
Cancel a pending login |
/logout |
Remove stored credentials for this chat |
/new |
Start a fresh Codex conversation |
/stop |
Stop the currently running Codex task |
/limits |
Show the latest Codex quota state for this chat |
/status |
Show bridge status |
/cron |
View and manage scheduled tasks |
/version |
Show installed Codex CLI version |
/update |
Force a Codex update check |
/help |
Show quick help |
Each Telegram chat gets its own Codex profile under /data/auth/<chat_id>/home/.codex.
Use this for public or multi-user deployments.
All chats reuse the same Codex profile.
Use this only for a private single-operator setup.
| Variable | Meaning |
|---|---|
TELEGRAM_BOT_TOKEN |
Telegram bot token |
TELEGRAM_ALLOWED_CHAT_IDS |
Comma-separated allowlist of allowed chats |
CODEX_AUTH_MODE |
per_chat or shared |
CODEX_AUTH_ROOT |
Root directory for per-chat auth |
CODEX_CONFIG_DIR |
Shared Codex profile path |
HOST_WORKSPACE |
Workspace path exposed inside the container |
HOST_SHELL_MODE |
host to execute generated shell commands against the real host |
CODEX_CHANNEL |
npm channel or exact version |
CODEX_MODEL |
Optional model override |
CODEX_EXTRA_ARGS |
Extra flags passed to codex exec |
PROGRESS_DISPLAY_MODE |
current for live state or log for stacked event history |
PROGRESS_IDLE_TEXT |
Text shown when Codex is thinking and no command is running |
PROGRESS_INCLUDE_AGENT_NOTES |
Whether to show short agent notes in the progress panel |
PROGRESS_MAX_ACTIVE_COMMANDS |
Maximum concurrent commands shown in current mode |
VOICE_LOCALE |
Default locale for voice input/output, for example en_US, es_ES, fr_FR |
STT_MODEL |
Faster-Whisper model name for audio transcription |
STT_LANGUAGE |
Optional transcription language hint. Leave it empty to derive it from VOICE_LOCALE |
STT_COMPUTE_TYPE |
Whisper compute mode, for example int8 on CPU |
STT_MODEL_DIR |
Directory where Faster-Whisper models are cached |
TTS_ENGINE |
Voice engine. piper is the recommended default |
TTS_PIPER_VOICE |
Optional explicit Piper voice. Leave it empty to auto-pick from VOICE_LOCALE |
TTS_PIPER_DIR |
Directory where Piper voice models are cached |
TTS_VOICE |
espeak-ng fallback voice used only if Piper fails |
TTS_SPEED |
espeak-ng fallback speed |
TTS_PITCH |
espeak-ng fallback pitch |
TTS_MAX_CHARS |
Maximum text length synthesized into a single audio reply |
You can keep voice setup simple by setting only VOICE_LOCALE.
Built-in presets:
| Locale | Auto-selected Piper voice |
|---|---|
en_US |
en_US-lessac-high |
en_GB |
en_GB-cori-high |
es_ES |
es_ES-sharvard-medium |
es_MX |
es_MX-claude-high |
es_AR |
es_AR-daniela-high |
fr_FR |
fr_FR-siwis-medium |
de_DE |
de_DE-thorsten-high |
it_IT |
it_IT-paola-medium |
pt_BR |
pt_BR-faber-medium |
pt_PT |
pt_PT-tugão-medium |
Examples:
VOICE_LOCALE=en_USVOICE_LOCALE=es_ESVOICE_LOCALE=fr_FRIf you want a specific Piper voice instead of the preset, set TTS_PIPER_VOICE directly.
The bridge supports both speech-to-text and text-to-speech:
- if the user sends a Telegram voice note or audio file, the bridge transcribes it and runs the transcript through Codex
- voice-driven chats get a voice reply back automatically
- normal text messages still get a text reply by default
Recommended voice setup:
VOICE_LOCALE=en_US
TTS_ENGINE=piper
TTS_PIPER_VOICE=
STT_LANGUAGE=This keeps the setup simple:
- transcription language is inferred from
VOICE_LOCALE - Piper auto-selects a matching voice preset
- the model files are cached under
STT_MODEL_DIRandTTS_PIPER_DIR
The Telegram progress panel is configurable from .env.
PROGRESS_DISPLAY_MODE=current
PROGRESS_IDLE_TEXT=thinking...
PROGRESS_INCLUDE_AGENT_NOTES=true
PROGRESS_MAX_ACTIVE_COMMANDS=6current: shows only what Codex is doing right nowlog: keeps the short stacked event historyPROGRESS_IDLE_TEXT: changes the text shown when Codex is thinking with no active commandsPROGRESS_INCLUDE_AGENT_NOTES: shows or hides short agent notes in the panelPROGRESS_MAX_ACTIVE_COMMANDS: limits how many concurrent commands are shown at once
The bridge keeps one active Codex thread per Telegram chat.
- normal messages continue the current conversation
/newstarts a fresh one
You can schedule prompts to run automatically from Telegram.
Examples:
/cron
/cron add docker-check | */30 * * * * | list all running docker containers
/cron pause <id>
/cron resume <id>
/cron delete <id>
/cron run <id>
Cron jobs are stored per chat and run using that chat's current login, model, and thinking settings.
The bridge checks the configured npm channel and updates Codex automatically when needed.
You can also force it manually:
/update
This bot can be very powerful depending on what you mount.
Be careful with:
TELEGRAM_ALLOWED_CHAT_IDSHOST_SHELL_MODE=host/var/run/docker.sock- shared Codex profiles
- aggressive
CODEX_EXTRA_ARGS
For public use, prefer:
CODEX_AUTH_MODE=per_chat- a strict allowlist
- exact version pinning when stability matters
docker-compose.yml- service definition.env.example- config templateservices/bridge/Dockerfile- runtime imageservices/bridge/app.py- Telegram bridgeservices/bridge/entrypoint.sh- container startupassets/logo.png- branding asset
Check:
docker compose logs -fThen confirm:
- the bot token is correct
- the chat id is allowlisted
- the container is running
Run:
/login
Check:
- Codex login completed successfully
- the workspace mount is correct
- Docker socket access exists if your task needs Docker
- your
CODEX_EXTRA_ARGSmake sense for the environment
