Skip to content

Commit 0125516

Browse files
authored
Give the database and firmware cache a real home (#73)
* feat: Give the images an explicit data directory The database and the firmware cache both defaulted to a path relative to the working directory, so they landed at /app/data inside the image tree, which no deployment surface mounts. Point both at /data instead, and create it owned by the app user so a freshly provisioned volume is writable after the image drops to uid 10001. The firmware cache has its own setting and is not derived from the data directory, so it needs its own variable; setting only SHELLY_DATA_DIR would move the database and leave the cache behind. The dev images get the same two variables so every image in the repo declares the same contract. * fix: Keep the compose database across a teardown The api service mounted only the two source trees, so the database lived in the container's own filesystem and every docker compose down took it with it. Give it a named volume at the data directory the image now declares, which a plain down keeps and only down -v removes. The dev image already sets SHELLY_DATA_DIR and SHELLY_FIRMWARE_DIR, so the service needs the volume and nothing else. * docs: Say where the data lives The deployment examples showed no volume and the configuration section described only browser localStorage, so nothing told a reader that the database and the firmware cache need somewhere to live. Name the directory and the two variables that set it, and mount it in every docker run and compose example, including the quick start printed into every GitHub release. The API reference gave SHELLY_FIRMWARE_DIR the package default, which stopped being what the image uses, and the wrong default host; both are corrected. A bind mounted host directory is called out separately, since it keeps its own ownership and the API runs as uid 10001. Anyone who mounted the old /app/data path by hand gets a note on where their database went, and the examples that were missing SHELLY_SECRET_KEY now set it, since without it the container exits at startup.
1 parent 037131e commit 0125516

9 files changed

Lines changed: 55 additions & 3 deletions

File tree

.github/workflows/release.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,12 @@ jobs:
3838
3939
## 🚀 Quick Start
4040
```bash
41-
# Run API server
42-
docker run -p 8000:8000 ${{ env.IMAGE_PREFIX }}-api:${{ env.IMAGE_TAG }}
41+
# Run API server. The -v keeps the database and firmware cache;
42+
# generate the key with: openssl rand -base64 32 | tr '+/' '-_'
43+
docker run -p 8000:8000 \
44+
-e SHELLY_SECRET_KEY="your-generated-key" \
45+
-v shelly-manager-data:/data \
46+
${{ env.IMAGE_PREFIX }}-api:${{ env.IMAGE_TAG }}
4347
4448
# Run CLI tool
4549
docker run --rm ${{ env.IMAGE_PREFIX }}-cli:${{ env.IMAGE_TAG }} --help

DEVELOPMENT.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ docker compose down cli
4141

4242
Both source trees are bind-mounted into that container, so edits on the host take effect without a rebuild.
4343

44+
The api service keeps its database and firmware cache in a named volume mounted at `/data`, so a plain `down` leaves both in place. `docker compose down -v` removes the volume too, which is how you get back to an empty database.
45+
4446
### Option 2: Local Development
4547

4648
1. **Install uv:**

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ services:
106106
# Defaults shown; set SHELLY_BACKUP_SCHEDULER_ENABLED=false to disable.
107107
- SHELLY_BACKUP_SCHEDULER_ENABLED=true
108108
- SHELLY_BACKUP_POLL_INTERVAL_SECONDS=60
109+
volumes:
110+
- shelly-manager-data:/data
109111

110112
shelly-manager-web:
111113
image: ghcr.io/jfmlima/shelly-manager-web:latest
@@ -115,6 +117,9 @@ services:
115117
- VITE_BASE_API_URL=http://localhost:8000
116118
depends_on:
117119
- shelly-manager-api
120+
121+
volumes:
122+
shelly-manager-data:
118123
```
119124
120125
**With Traefik**:
@@ -132,6 +137,8 @@ services:
132137
# Defaults shown; set SHELLY_BACKUP_SCHEDULER_ENABLED=false to disable.
133138
- SHELLY_BACKUP_SCHEDULER_ENABLED=true
134139
- SHELLY_BACKUP_POLL_INTERVAL_SECONDS=60
140+
volumes:
141+
- shelly-manager-data:/data
135142
labels:
136143
- "traefik.enable=true"
137144
- "traefik.http.routers.shelly-manager-api.rule=Host(`shelly-manager-api.your.domain`)"
@@ -152,6 +159,9 @@ services:
152159
- "traefik.http.routers.shelly-manager-web.service=shelly-manager-web"
153160
- "traefik.http.routers.shelly-manager-web.entrypoints=web"
154161
- "traefik.http.services.shelly-manager-web.loadbalancer.server.port=8080"
162+
163+
volumes:
164+
shelly-manager-data:
155165
```
156166
157167
**Home Assistant Add-on**
@@ -202,15 +212,22 @@ docker run --rm -it \
202212
```bash
203213
docker run -p 8000:8000 \
204214
-e SHELLY_SECRET_KEY="your-generated-key" \
215+
-v shelly-manager-data:/data \
205216
ghcr.io/jfmlima/shelly-manager-api:latest
206217
```
207218

219+
The `-v` is what keeps your data. The image writes everything it stores to `/data`, and without a volume there that directory belongs to the container and goes away with it. A named volume like the one above picks up the right ownership from the image; if you bind mount a host directory instead, run `chown 10001:10001` on it first, because the API runs as that user and cannot write to a root owned directory.
220+
208221
### Configuration
209222

210223
Shelly Manager is zero-configuration by default. All scan and management parameters are provided at runtime via the Web UI, CLI flags, API parameters or ENV variables. This ensures flexibility and removes the need for managing static configuration files.
211224

212225
For persistent storage of discovered devices in the Web UI, the application leverages browser localStorage. For API-based integrations, the client is responsible for maintaining device lists.
213226

227+
**Server-side state lives in `/data`.** Device credentials, configuration backups, backup schedules and provisioning profiles are kept in a SQLite database at `/data/data.db`, and downloaded firmware bundles under `/data/firmware`. The API and CLI images set `SHELLY_DATA_DIR=/data` and `SHELLY_FIRMWARE_DIR=/data/firmware`; mount a volume at `/data` and everything survives a restart, or point the two variables somewhere else if you prefer another path. Outside a container both default to `./data`, relative to the working directory. The Unraid image and the Home Assistant add-on set their own paths and handle this for you.
228+
229+
Earlier images wrote to `/app/data` instead. If you carried that over with a mount of your own, such as `-v ./data:/app/data`, your database will look reset after this upgrade: it is still at the old path, and moving `data.db` and `firmware/` into the new volume brings it back.
230+
214231
**Scheduled backups** run on the API server itself. When `SHELLY_BACKUP_SCHEDULER_ENABLED` is `true` (the default), an in-process poller captures backups for any due schedules every `SHELLY_BACKUP_POLL_INTERVAL_SECONDS` (default 60). Because the timer lives in-process, run the API as a single worker (the default); see the [API README](packages/api/README.md) for the full setting reference.
215232

216233
**Local firmware updates** let a device that cannot reach the internet still be updated. Ask for one with `"source": "local"` on the update endpoint, or `--source local` from the CLI: the manager downloads the official firmware from Shelly once, keeps it, and tells the device to fetch it from the manager instead. The same copy serves every device running that model, so only the manager needs internet access.

docker-compose.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ services:
2121
# internet update source works.
2222
SHELLY_FIRMWARE_ADVERTISED_BASE_URL: "${SHELLY_FIRMWARE_ADVERTISED_BASE_URL:-}"
2323
volumes:
24+
- shelly-data:/data
2425
- ./packages/api/src:/app/packages/api/src:rw
2526
- ./packages/core/src:/app/packages/core/src:rw
2627
ports:
@@ -66,3 +67,6 @@ services:
6667
- cli
6768
entrypoint: ["tail", "-f", "/dev/null"]
6869
restart: unless-stopped
70+
71+
volumes:
72+
shelly-data:

packages/api/Dockerfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ COPY --from=builder --chown=app:app /app/packages/api /app/packages/api
2727
ENV PATH="/app/.venv/bin:$PATH"
2828
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
2929
ENV HOST=0.0.0.0 PORT=8000 DEBUG=false
30+
ENV SHELLY_DATA_DIR=/data SHELLY_FIRMWARE_DIR=/data/firmware
31+
32+
RUN mkdir -p /data/firmware && chown -R app:app /data
3033

3134
WORKDIR /app
3235
USER app

packages/api/README.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,8 @@ curl -X POST "http://localhost:8000/api/backups/1/restore" \
443443
| `SHELLY_BACKUP_SCHEDULER_ENABLED` | `true` | Run the in-process scheduled-backup poller |
444444
| `SHELLY_BACKUP_POLL_INTERVAL_SECONDS` | `60` | How often the scheduler checks for due backups |
445445
| `SHELLY_FIRMWARE_ADVERTISED_BASE_URL` | (none) | URL devices use to reach this API, e.g. `http://192.168.1.50:8000`. Required for local updates; it cannot be guessed |
446-
| `SHELLY_FIRMWARE_DIR` | `./data/firmware` | Where downloaded firmware bundles are kept |
446+
| `SHELLY_DATA_DIR` | `/data` in the image, `./data` otherwise | Directory holding the SQLite database (`data.db`) |
447+
| `SHELLY_FIRMWARE_DIR` | `/data/firmware` in the image, `./data/firmware` otherwise | Where downloaded firmware bundles are kept |
447448
| `SHELLY_FIRMWARE_INDEX_URL` | `https://updates.shelly.cloud/update` | Where published firmware is looked up, by the app name a device reports |
448449
| `SHELLY_FIRMWARE_ALLOWED_DOWNLOAD_HOSTS` | `shelly.cloud` | Comma separated hosts firmware may be downloaded from, matched exactly or as a parent domain and re-checked on every redirect. `*` accepts any host |
449450
| `SHELLY_FIRMWARE_VERIFY_SSL` | `false` | Verify TLS when talking to the firmware index and CDN. Off by default because Shelly signs those hosts with a private CA absent from public trust stores; devices verify firmware signatures themselves |
@@ -453,6 +454,14 @@ stored in the local database (`{data_dir}/data.db`); rotating the key makes exis
453454
undecryptable. Set `SHELLY_BACKUP_SCHEDULER_ENABLED=false` to turn off automated backups while
454455
still managing schedules through the API.
455456

457+
Both directories are created on first use and both need to survive a restart, so mount a
458+
volume at `/data` when running this image. Without one they belong to the container, and
459+
replacing it resets the database and empties the firmware cache. A named volume inherits the
460+
image's ownership; a bind mounted host directory does not, so `chown 10001:10001` it before
461+
first start or the API cannot write to it. Packaged builds set their own path: the Unraid
462+
image uses `/config`, the Home Assistant add-on uses the add-on data volume, and neither
463+
needs a mount configured by hand.
464+
456465
## Docker Deployment
457466

458467
### Basic Deployment
@@ -463,6 +472,8 @@ docker run -d \
463472
-p 8000:8000 \
464473
-e HOST=0.0.0.0 \
465474
-e PORT=8000 \
475+
-e SHELLY_SECRET_KEY="your-generated-key" \
476+
-v shelly-manager-data:/data \
466477
ghcr.io/jfmlima/shelly-manager-api:latest
467478
```
468479

@@ -478,7 +489,13 @@ services:
478489
- HOST=0.0.0.0
479490
- PORT=8000
480491
- DEBUG=false
492+
- SHELLY_SECRET_KEY=your-generated-key
493+
volumes:
494+
- shelly-manager-data:/data
481495
restart: unless-stopped
496+
497+
volumes:
498+
shelly-manager-data:
482499
```
483500
484501
### Health Check

packages/api/dev.Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ RUN uv sync --frozen --package shelly-manager-api
1515
ENV PATH="/app/.venv/bin:$PATH"
1616
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
1717
ENV HOST=0.0.0.0 PORT=8000 DEBUG=true
18+
ENV SHELLY_DATA_DIR=/data SHELLY_FIRMWARE_DIR=/data/firmware
1819

1920
EXPOSE 8000
2021

packages/cli/Dockerfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ COPY --from=builder --chown=app:app /app/packages/cli /app/packages/cli
2424

2525
ENV PATH="/app/.venv/bin:$PATH"
2626
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
27+
ENV SHELLY_DATA_DIR=/data SHELLY_FIRMWARE_DIR=/data/firmware
28+
29+
RUN mkdir -p /data/firmware && chown -R app:app /data
2730

2831
WORKDIR /app
2932
USER app

packages/cli/dev.Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ RUN uv sync --frozen --package shelly-manager-cli
1313

1414
ENV PATH="/app/.venv/bin:$PATH"
1515
ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
16+
ENV SHELLY_DATA_DIR=/data SHELLY_FIRMWARE_DIR=/data/firmware
1617

1718
ENTRYPOINT ["shelly-manager"]

0 commit comments

Comments
 (0)