Context
ghst mcp http is increasingly deployed in headless environments (Docker on Coolify / fly.io / Railway, CI workers, etc.) where the canonical configuration mechanism is environment variables.
For single-site deployments this works natively: ghst reads GHOST_URL and GHOST_STAFF_ACCESS_TOKEN at runtime (see src/lib/config.ts:358). One docker run with two env vars and you're done — clean.
For multi-site deployments (e.g. mirroring a blog FR/EN translated variant, or hosting two related blogs in one container) there is no equivalent. The only path today is to script ghst auth login calls at container startup.
Current workaround
FROM node:20-alpine
RUN npm install -g @tryghost/ghst@0.13.0
EXPOSE 3100
CMD ["sh", "-c", "\
ghst auth login --non-interactive --insecure-storage \
--url \"$GHOST_URL_1\" --staff-token \"$GHOST_TOKEN_1\" --site \"$GHOST_ALIAS_1\" && \
ghst auth login --non-interactive --insecure-storage \
--url \"$GHOST_URL_2\" --staff-token \"$GHOST_TOKEN_2\" --site \"$GHOST_ALIAS_2\" && \
ghst mcp http --host 0.0.0.0 --port 3100 --tools all --auth-token $GHST_AUTH_TOKEN --unsafe-public-bind \
"]
This works but has several rough edges:
- Forced
--insecure-storage because containers have no OS keychain, which generates a warning every boot:
Warning: secure credential storage is unavailable; plaintext staff access tokens remain in config.
- Implicit default site by login order — the last
ghst auth login wins as active. If you add a third site later and forget the ordering, the default silently changes.
- Runtime config mutation — each boot rewrites
~/.config/ghst/config.json, which is a side effect on a stateless container.
- Naming friction — env var names (
GHOST_URL_1, GHOST_TOKEN_1, GHOST_ALIAS_1, etc.) are arbitrary and user-defined in the startup script, not standardized.
Proposal
Add a native mechanism for declaring multiple sites via environment variables, that ghst reads on startup without any auth login step. Two options I see, not mutually exclusive:
Option A — JSON-encoded env var
GHOST_SITES='[
{"alias":"blog-fr","url":"https://blog.example.com","staffToken":"id:secret","default":true},
{"alias":"blog-en","url":"https://en.example.com","staffToken":"id:secret"}
]'
Pros: single variable, clean. Cons: shell escaping for special chars (rare for Ghost tokens but possible).
Option B — Discovery via numbered or named env vars
GHOST_SITE_BLOG_FR_URL=https://blog.example.com
GHOST_SITE_BLOG_FR_STAFF_TOKEN=id:secret
GHOST_SITE_BLOG_EN_URL=https://en.example.com
GHOST_SITE_BLOG_EN_STAFF_TOKEN=id:secret
GHOST_DEFAULT_SITE=blog-fr
Pros: each value is a plain string, no escaping. Ergonomic in Coolify / fly secrets UIs. Cons: more variables to manage.
In both cases, an explicit GHOST_DEFAULT_SITE (or equivalent in the JSON) replaces the implicit "last login wins" rule.
Why this matters
- Eliminates the
--insecure-storage warning by making the env-vars path first-class for multi-site (no plaintext config file written).
- Removes the brittle startup script and runtime config mutation.
- Makes the default site explicit, removing a class of "oops, wrong blog" bugs that matter when destructive tools are exposed via MCP.
- Aligns multi-site deployments with the same pattern that already works for single-site.
Environment
- ghst v0.13.0
- Deployment context: Docker on Coolify, multi-site setup (e.g. FR ↔ EN mirror translation use case via Claude.ai MCP connector)
Happy to test a patch or contribute one if there's interest and direction on the preferred API shape.
Context
ghst mcp httpis increasingly deployed in headless environments (Docker on Coolify / fly.io / Railway, CI workers, etc.) where the canonical configuration mechanism is environment variables.For single-site deployments this works natively: ghst reads
GHOST_URLandGHOST_STAFF_ACCESS_TOKENat runtime (seesrc/lib/config.ts:358). Onedocker runwith two env vars and you're done — clean.For multi-site deployments (e.g. mirroring a blog FR/EN translated variant, or hosting two related blogs in one container) there is no equivalent. The only path today is to script
ghst auth logincalls at container startup.Current workaround
This works but has several rough edges:
--insecure-storagebecause containers have no OS keychain, which generates a warning every boot:ghst auth loginwins asactive. If you add a third site later and forget the ordering, the default silently changes.~/.config/ghst/config.json, which is a side effect on a stateless container.GHOST_URL_1,GHOST_TOKEN_1,GHOST_ALIAS_1, etc.) are arbitrary and user-defined in the startup script, not standardized.Proposal
Add a native mechanism for declaring multiple sites via environment variables, that ghst reads on startup without any
auth loginstep. Two options I see, not mutually exclusive:Option A — JSON-encoded env var
Pros: single variable, clean. Cons: shell escaping for special chars (rare for Ghost tokens but possible).
Option B — Discovery via numbered or named env vars
Pros: each value is a plain string, no escaping. Ergonomic in Coolify / fly secrets UIs. Cons: more variables to manage.
In both cases, an explicit
GHOST_DEFAULT_SITE(or equivalent in the JSON) replaces the implicit "last login wins" rule.Why this matters
--insecure-storagewarning by making the env-vars path first-class for multi-site (no plaintext config file written).Environment
Happy to test a patch or contribute one if there's interest and direction on the preferred API shape.