Deploy immutably when asked to #118

Closed
opened 2026-08-28 05:04:12 +00:00 by jcoffey-dev · 0 comments
Owner

Follow-on to #117. That PR made an immutable container possible; this makes it deployable, which it was not — ihasmail-deploy.sh mounted the data volume unconditionally, so any redeploy would have quietly restored a mutable container underneath you.

IHASMAIL_IMMUTABLE=1 switches the run line:

0 →  docker run -d --name ihasmail … --env-file … -v ihasmail-data:/data  ihasmail:TAG
1 →  docker run -d --name ihasmail … --env-file … --read-only --tmpfs /tmp -e IMMUTABLE=1 -e SESSION_FILE=  ihasmail:TAG

Rollback is the same variable

IHASMAIL_IMMUTABLE=0 ./ihasmail-deploy.sh --yes

Verified that the 0 branch reproduces the current production run line exactly — -v ihasmail-data:/data, readonly=false, restart=unless-stopped. The named volume is never touched in either mode, so the sessions in it when the switch is thrown are still there to come back to.

Why -e rather than editing the environment file

.env.production sets SESSION_FILE, and the image sets it too. Confirmed -e takes precedence over --env-file:

--env-file (SESSION_FILE=/data/sessions.json) + -e SESSION_FILE=  →  SESSION_FILE=[]

So the switch stays one variable in one place instead of two things that have to agree.

The guard earns its keep here

IMMUTABLE=1 reaches the server, which checks the claim rather than believing it. A half-applied switch — the flag without --read-only, or a SESSION_FILE that survived — fails at startup with a message naming the fix, instead of looking healthy until the next redeploy signs everyone out.

Cost, stated plainly

Sessions do not outlive a deploy in this mode; there is nowhere to keep them. Everyone signs in again on each deploy. That goes away when OAuth moves the session into a token Stalwart issues and can revoke.

Note on rollout

deploy.example.sh re-execs itself from /tmp before git reset --hard, so the running copy is always the previous one. The first deploy after this merges updates the checkout but still uses the old run line; the second picks up the new one. Two deploys to take effect, by design.

Merged 2026-08-27 as coffey-labs/ihasmail@d6aa4d543a

Rebuilt from: git history, session transcript.

Follow-on to #117. That PR made an immutable container *possible*; this makes it **deployable**, which it was not — `ihasmail-deploy.sh` mounted the data volume unconditionally, so any redeploy would have quietly restored a mutable container underneath you. `IHASMAIL_IMMUTABLE=1` switches the run line: ``` 0 → docker run -d --name ihasmail … --env-file … -v ihasmail-data:/data ihasmail:TAG 1 → docker run -d --name ihasmail … --env-file … --read-only --tmpfs /tmp -e IMMUTABLE=1 -e SESSION_FILE= ihasmail:TAG ``` ### Rollback is the same variable ```bash IHASMAIL_IMMUTABLE=0 ./ihasmail-deploy.sh --yes ``` Verified that the `0` branch reproduces the current production run line exactly — `-v ihasmail-data:/data`, `readonly=false`, `restart=unless-stopped`. The named volume is never touched in either mode, so the sessions in it when the switch is thrown are still there to come back to. ### Why `-e` rather than editing the environment file `.env.production` sets `SESSION_FILE`, and the image sets it too. Confirmed `-e` takes precedence over `--env-file`: ``` --env-file (SESSION_FILE=/data/sessions.json) + -e SESSION_FILE= → SESSION_FILE=[] ``` So the switch stays one variable in one place instead of two things that have to agree. ### The guard earns its keep here `IMMUTABLE=1` reaches the server, which checks the claim rather than believing it. A half-applied switch — the flag without `--read-only`, or a `SESSION_FILE` that survived — fails at startup with a message naming the fix, instead of looking healthy until the next redeploy signs everyone out. ### Cost, stated plainly Sessions do not outlive a deploy in this mode; there is nowhere to keep them. Everyone signs in again on each deploy. That goes away when OAuth moves the session into a token Stalwart issues and can revoke. ### Note on rollout `deploy.example.sh` re-execs itself from `/tmp` before `git reset --hard`, so the *running* copy is always the previous one. The first deploy after this merges updates the checkout but still uses the old run line; the second picks up the new one. Two deploys to take effect, by design. **Merged** 2026-08-27 as coffey-labs/ihasmail@d6aa4d543a77 <sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.