This guide migrates a Bichon v1.x installation to the v2.x storage architecture. It is run interactively through the bundled bichon-admin tool and is non-destructive — legacy files are read and copied, never modified or deleted.
How to tell you are on v1.x
Your data directory already looks mostly modern — Tantivy indexes and the metadata store are the same as v2.x:
<root-dir>/
├── memdb/ # accounts, users, roles, mailboxes…
├── bichon-indices/
│ ├── mail_metadata/ # Tantivy mail index
│ └── attachment_metadata/ # Tantivy attachment index
└── bichon-storage/ # ← the Fjall blob database (this is what changes)
├── email/ # fjall keyspace: raw EML blobs
└── attachments/ # fjall keyspace: attachment blobsThe difference from v2.x: in v1.x the raw blobs live in the Fjall key-value store, keyed by 64-character hex BLAKE3 content hashes. v2.x replaces this with the bichon-blob engine. There is no blobs/ directory inside bichon-storage/ yet, and either no STORAGE_VERSION file or one containing 1.
A v1.x install that predates the
STORAGE_VERSIONfile gets one automatically: the first time a v2.x server starts and sees a non-emptybichon-storage/, it writesSTORAGE_VERSION=1and enters maintenance mode. That is expected.
What the migration does
Exactly one thing, and it is narrow by design: it copies blobs.
- The Fjall database is opened read-only and both keyspaces are scanned (
email, thenattachments). - Each 64-char hex key is converted to its raw 32-byte form, and each blob is written into the new bichon-blob engine at
<data-dir>/bichon-storage/blobs/, re-compressed with zstd. - Tantivy indexes (
bichon-indices/) and metadata (memdb/) are not touched — they are already v2.x-compatible.
When it finishes, STORAGE_VERSION is set to 2, which is what lets the v2.x server start normally.
Empty values are skipped silently. Blobs larger than 100 MB are skipped with a warning and will not exist in the new archive — note them if you see any.
Custom directory configurations
⚠ Read this before starting if your old server used custom paths. Bichon supports moving the blob store off the root with
--bichon-data-dir(env varBICHON_DATA_DIR), and the indexes with--bichon-index-dir(BICHON_INDEX_DIR). The blob migration is the only step that touches data, so--bichon-data-diris the one that matters here.
1. Find out what your old server used. Check the startup configuration first — docker inspect <container> (look for BICHON_DATA_DIR / BICHON_INDEX_DIR in Env), your docker-compose.yml, systemd unit, or launch script. In a default setup neither is set, and the blob store lives at <root>/bichon-storage.
2. Answer the tool's prompts with the old server's exact values. The tool asks for --bichon-root-dir and --bichon-data-dir (leave the data dir blank only if the old server did not set one). It then expects the Fjall database at <data-dir>/bichon-storage — a wrong path aborts with "Is this really a v1.x install?" and changes nothing. It never asks for the index dir: Tantivy indexes are not touched by this migration, wherever they live.
3. Know where the new data lands. The new blob engine is created inside the same directory as the Fjall database:
| Setting | Fjall database at | New v2.x blobs at |
|---|---|---|
| (none set) | <root>/bichon-storage | <root>/bichon-storage/blobs |
--bichon-data-dir=/hdd | /hdd/bichon-storage | /hdd/bichon-storage/blobs |
4. Start the new server with the same flags. The v2.x server resolves its blob store as <data-dir>/bichon-storage — identical to the tool — so it finds the migrated blobs only if you keep passing the identical flag (-e BICHON_DATA_DIR=/hdd in Docker). If the old server also used a custom --bichon-index-dir, keep passing that too, or the server will not find its Tantivy indexes.
⚠ The empty-archive trap: the migration writes
STORAGE_VERSION=2into the root directory no matter where the blobs live. If you then start the v2.x server without the custom flags, the version check passes, the server creates a fresh empty store under<root>, and the WebUI shows an empty archive — while your data sits untouched at the custom location. Stop the server immediately in that state, restart it with the correct flags, and the archive reappears. Do not let IMAP accounts re-sync into the empty archive in the meantime.
The cleanup step (below) also moves accordingly: after verification, delete the old Fjall files inside <data-dir>/bichon-storage/ (everything except blobs/) — at the custom location, not under the root.
Step-by-step
1. Get the server into a stopped-but-reachable state
Upgrade to the v2.x image/binary and start it. It detects the v1.x layout and enters maintenance mode — the WebUI shows a 503 page and the container keeps running, so docker exec works.
In the default Docker setup the data directory is
/datainside the container. All paths the tool asks for are container-internal paths when you run it viadocker exec.
2. Start the tool
docker exec -it <bichon-container> bichon-admin(or ./bichon-admin from the install directory on a bare install)
Select "Migrate v1.x Storage to v2.x (Fjall → bichon-blob)".
3. Answer the path prompts
| Prompt | Default (Enter to accept) | Notes |
|---|---|---|
--bichon-root-dir | — (required) | Must match what the server uses. In Docker: /data |
--bichon-data-dir | the root directory | Only override if the server was started with a custom --bichon-data-dir |
The tool then locates the Fjall database at <data-dir>/bichon-storage and the new blob engine target at <data-dir>/bichon-storage/blobs.
4. Pick a batch size
The batch size controls how many blobs are buffered in memory per write:
- Higher = faster, more RAM
- Lower = slower, less RAM
- Default 1000 is fine for most installs
5. Watch the progress
Two progress lines, one per keyspace: Email: N blobs migrated... and Attachment: N blobs migrated..., finishing with a summary like "51234 email blobs, 8712 attachment blobs migrated".
6. Restart and verify
docker restart <bichon-container>The server now starts normally. Verify in the WebUI that message previews load (this exercises blob reads), attachments open, and search returns results.
7. Clean up (only after verifying)
The old Fjall files are still inside bichon-storage/. Once the new install is verified healthy, everything in that directory except blobs/ can be deleted:
<data-dir>/bichon-storage/email/
<data-dir>/bichon-storage/attachments/
<data-dir>/bichon-storage/… # any other fjall internal files/partitionsKeep bichon-storage/blobs/ — that is the live v2.x blob store. And do not delete anything before verification; the legacy files are your rollback plan.
Troubleshooting
| Symptom | Meaning / fix |
|---|---|
"Fjall database not found at <…>/bichon-storage. Is this really a v1.x install?" | Wrong data dir — or your data is actually from v0.3.7 (no bichon-storage/ at all); use the v0.3.7 guide instead |
"Target blob directory <…>/blobs already exists" | You have already migrated. If the new server works, just remove the old fjall files (see cleanup). To redo the migration from scratch, delete blobs/ and re-run |
WARN: skipping oversized blob | A blob over 100 MB was left behind. If you need it, re-import those messages through the normal import path |
| Migration interrupted (Ctrl-C, OOM, power loss) | Fjall files are untouched. Delete the partially written <data-dir>/bichon-storage/blobs/ and re-run — do not delete the fjall keyspaces |
| Server still shows the 503 page after restart | STORAGE_VERSION was not written (the tool prints an error if this fails). Check docker logs, fix, and re-run |
Disk space
The migration writes a full copy of all blobs in the new store (zstd-compressed, so typically somewhat smaller than the fjall copy), while the legacy files stay in place. Have at least as much free space as your current bichon-storage/ size before starting.