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.
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:
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.
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.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Follow-on to #117. That PR made an immutable container possible; this makes it deployable, which it was not —
ihasmail-deploy.shmounted the data volume unconditionally, so any redeploy would have quietly restored a mutable container underneath you.IHASMAIL_IMMUTABLE=1switches the run line:Rollback is the same variable
Verified that the
0branch 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
-erather than editing the environment file.env.productionsetsSESSION_FILE, and the image sets it too. Confirmed-etakes precedence over--env-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=1reaches the server, which checks the claim rather than believing it. A half-applied switch — the flag without--read-only, or aSESSION_FILEthat 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.shre-execs itself from/tmpbeforegit 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.