Skip to content

Commit b63bb3d

Browse files
jkyberneeesclaude
andcommitted
docs(docker): use external ./.odek folder for Telegram state, drop named volume
Replace the odek-tg-state named volume with a local ./.odek bind mount (an external host folder like ./workspace) for both Telegram services. Add docker/.odek/ with a .gitkeep, gitignore its contents, and sync the guide, README, and Dockerfile comment to the folder-based model (layout, gitignore note, compose snippet, mount explanation, and singleton-lock gotcha). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent ae95736 commit b63bb3d

6 files changed

Lines changed: 40 additions & 31 deletions

File tree

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ odek-bin
88
notify-channel-proposal.md
99
internal/mcpclient/testdata/fakeserver
1010

11-
# Docker Compose examples — local secrets and agent scratch dir
11+
# Docker Compose examples — local secrets and agent scratch dirs
1212
docker/.env
1313
docker/workspace/*
1414
!docker/workspace/.gitkeep
15+
docker/.odek/*
16+
!docker/.odek/.gitkeep

DOCKER_COMPOSE_USER_GUIDE.md

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,12 @@ odek/
4040
├── .env # your API key + model settings (gitignored)
4141
├── config.restricted.json # Restricted permission policy
4242
├── config.godmode.json # Godmode (YOLO) permission policy
43-
└── workspace/ # the directory the agent works in (mounted into the container)
43+
├── workspace/ # the directory the agent works in (mounted into the container)
44+
└── .odek/ # Telegram bot state: sessions, skills, lock (mounted in)
4445
```
4546

46-
> Add `.env` and `workspace/` to your `.gitignore` so you never commit secrets or
47-
> scratch files.
47+
> Add `.env`, `workspace/`, and `.odek/` to your `.gitignore` so you never commit secrets
48+
> or scratch files.
4849
4950
---
5051

@@ -75,8 +76,8 @@ FROM alpine:latest
7576
RUN apk add --no-cache ca-certificates git bash coreutils curl jq
7677

7778
# Run as a non-root user — defense in depth even inside the container.
78-
# Pre-create ~/.odek owned by the user so a mounted named volume (used for
79-
# Telegram session state in §13) inherits uid 1000 ownership and is writable.
79+
# Pre-create ~/.odek owned by the user so it's writable for config, sessions,
80+
# and the Telegram lock (whether backed by an image dir or a mounted folder).
8081
RUN adduser -D -u 1000 odek \
8182
&& mkdir -p /home/odek/.odek /workspace \
8283
&& chown -R odek:odek /home/odek/.odek /workspace
@@ -452,9 +453,9 @@ ODEK_TELEGRAM_SESSION_TTL_HOURS=24 # optional
452453

453454
### 13c. Compose services
454455

455-
Add these to `docker-compose.yml`. Note the **named volume for `/home/odek/.odek`**: it
456-
persists per‑chat sessions, the daily‑budget counter, and the singleton lock across
457-
restarts. No `ports` are needed.
456+
Add these to `docker-compose.yml`. State (per‑chat sessions, the daily‑budget counter, and
457+
the singleton lock) lives in a local **`./.odek` folder** — an external host folder, just
458+
like `./workspace` — so it survives restarts and is easy to inspect. No `ports` are needed.
458459

459460
```yaml
460461
# ── Telegram bot — Restricted (approvals via inline keyboards) ──
@@ -466,8 +467,8 @@ restarts. No `ports` are needed.
466467
command: ["telegram"]
467468
volumes:
468469
- ./workspace:/workspace
470+
- ./.odek:/home/odek/.odek
469471
- ./config.restricted.json:/home/odek/.odek/config.json:ro
470-
- odek-tg-state:/home/odek/.odek
471472
restart: unless-stopped
472473
473474
# ── Telegram bot — Godmode (no prompts; disposable container) ──
@@ -479,19 +480,22 @@ restarts. No `ports` are needed.
479480
command: ["telegram"]
480481
volumes:
481482
- ./workspace:/workspace
483+
- ./.odek:/home/odek/.odek
482484
- ./config.godmode.json:/home/odek/.odek/config.json:ro
483-
- odek-tg-state:/home/odek/.odek
484485
restart: unless-stopped
486+
```
487+
488+
Create the folder first (so the container's non‑root user can write to it) and gitignore
489+
its contents:
485490

486-
volumes:
487-
odek-tg-state:
491+
```bash
492+
mkdir -p .odek && chmod 777 .odek && touch .odek/.gitkeep
488493
```
489494

490-
> The `:ro` config mount and the writable `odek-tg-state` volume both target
491-
> `/home/odek/.odek`. Compose layers them: the bind mount wins for `config.json`, the
492-
> named volume holds everything else (sessions, lock, budget). If your Compose version
493-
> errors on overlapping mounts, drop the `:ro` bind and instead bake the policy into the
494-
> image, or copy it into the volume once at startup.
495+
> The `./.odek` bind mounts at `/home/odek/.odek`, and `config.json` is bind‑mounted on top
496+
> of it — a nested file‑over‑directory mount. Compose layers them: the `:ro` `config.json`
497+
> wins for that one file, and `./.odek` holds everything else (sessions, lock, budget).
498+
> Docker leaves a harmless empty `./.odek/config.json` stub on the host as the mount point.
495499

496500
### 13d. Run it
497501

@@ -522,16 +526,16 @@ Stop either with `docker compose --profile telegram-restricted down` (matching p
522526
| `/help` | List all commands |
523527
| `/new` | Archive the current session, start fresh |
524528

525-
Voice and photo messages are supported too. Sessions persist per chat (visible via
526-
`odek session list` against the mounted state volume).
529+
Voice and photo messages are supported too. Sessions persist per chat in the local
530+
`./.odek` folder (inspect with `odek session list` against that directory).
527531

528532
### 13f. Telegram‑specific gotchas
529533

530534
- **One poller per token.** Telegram allows a single long‑poller per bot token; a second
531535
one gets `409 Conflict`. So you **cannot run the Restricted and Godmode bot services at
532536
the same time with the same token** — pick one, or create a second bot via @BotFather
533-
for the other. A singleton PID lock at `~/.odek/telegram.pid` (kept in the shared state
534-
volume) backs this up — a second `odek telegram` that finds a live lock won't start.
537+
for the other. A singleton PID lock at `~/.odek/telegram.pid` (kept in the shared `./.odek`
538+
folder) backs this up — a second `odek telegram` that finds a live lock won't start.
535539
- **Optional health endpoint.** The `telegram` command takes no CLI flags — configure it
536540
via env. Set `ODEK_TELEGRAM_HEALTH_ADDR=0.0.0.0:9090` in `.env` (and add a `ports:`
537541
mapping) to expose `GET /health` for an orchestrator's liveness probe.

docker/.odek/.gitkeep

Whitespace-only changes.

docker/Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ RUN apk add --no-cache ca-certificates git bash coreutils curl jq
6363
# (the mkdir/chown, ENV HOME, USER, and WORKDIR lines all work unchanged).
6464

6565
# Run as a non-root user — defense in depth even inside the container.
66-
# Pre-create ~/.odek owned by the user so a mounted named volume (used for
67-
# Telegram session state) inherits uid 1000 ownership and is writable.
66+
# Pre-create ~/.odek owned by the user so it's writable for config, sessions,
67+
# and the Telegram lock (whether backed by an image dir or a mounted folder).
6868
RUN adduser -D -u 1000 odek \
6969
&& mkdir -p /home/odek/.odek /workspace \
7070
&& chown -R odek:odek /home/odek/.odek /workspace

docker/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ docker compose --profile telegram-restricted up --build -d # approvals in chat
9292
docker compose --profile telegram-godmode up --build -d # no prompts
9393
```
9494

95-
Message your bot `/start`. Sessions persist in the `odek-tg-state` volume.
95+
Message your bot `/start`. State (sessions, skills, `telegram.pid`) persists in the
96+
local `./.odek` folder — an external host folder, just like `./workspace`.
9697

9798
> **Only run one Telegram profile at a time per token** — Telegram allows a single
9899
> long-poller per bot (a second gets `409 Conflict`). Create a second bot via

docker/docker-compose.yml

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ services:
5151
restart: "no"
5252

5353
# ── Telegram bot — Restricted (approvals via inline keyboards) ──
54+
# State (sessions, skills, telegram.pid) lives in the local ./.odek folder —
55+
# an external folder on the host, just like ./workspace. config.restricted.json
56+
# is layered on top of ./.odek/config.json to set the approval policy.
5457
odek-telegram-restricted:
5558
profiles: ["telegram-restricted"]
5659
build:
@@ -61,11 +64,13 @@ services:
6164
command: ["telegram"]
6265
volumes:
6366
- ./workspace:/workspace
67+
- ./.odek:/home/odek/.odek
6468
- ./config.restricted.json:/home/odek/.odek/config.json:ro
65-
- odek-tg-state:/home/odek/.odek
6669
restart: unless-stopped
6770

68-
# ── Telegram bot — Godmode (no prompts; disposable container) ──
71+
# ── Telegram bot — Godmode (no prompts; unrestricted) ──
72+
# Same ./.odek state folder; config.godmode.json is layered on top of
73+
# ./.odek/config.json to force the unrestricted (action: allow) policy.
6974
odek-telegram-godmode:
7075
profiles: ["telegram-godmode"]
7176
build:
@@ -76,9 +81,6 @@ services:
7681
command: ["telegram"]
7782
volumes:
7883
- ./workspace:/workspace
84+
- ./.odek:/home/odek/.odek
7985
- ./config.godmode.json:/home/odek/.odek/config.json:ro
80-
- odek-tg-state:/home/odek/.odek
8186
restart: unless-stopped
82-
83-
volumes:
84-
odek-tg-state:

0 commit comments

Comments
 (0)