Lightweight tinybird setup wizard and Coolify Docker support for Ghost-6#206
Lightweight tinybird setup wizard and Coolify Docker support for Ghost-6#206BadPirate wants to merge 4 commits into
Conversation
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (12)
WalkthroughThis PR introduces a complete Ghost 6 deployment system with integrated Tinybird analytics for Coolify. It adds MySQL provisioning scripts that idempotently create users, databases, and permissions; a one-shot MySQL sidecar Docker image; and two Express-based services (ghost-gate and wizard) that both bootstrap MySQL and proxy traffic while providing a browser-based Tinybird configuration UI. Docker Compose templates orchestrate a multi-service stack including Ghost, MySQL, Tinybird analytics, and ActivityPub, with health checks and dependency ordering. Multi-stage Dockerfiles bundle the Tinybird CLI installer for each service. Documentation covers new Ghost 6 installations and upgrade paths from Ghost 5 template-based setups. Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsGit: Failed to clone repository. Please run the Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 19
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@coolify/deploy_local.sh`:
- Around line 3-5: The script fails to quote path/arg expansions; change
COOLIFY_DIR assignment to use dirname "$0" (e.g., COOLIFY_DIR=$(dirname "$0"))
and quote usages in the docker compose invocation: --project-directory
"$COOLIFY_DIR/.." and -f "$COOLIFY_DIR/docker-compose.6.yml" -f
"$COOLIFY_DIR/docker-compose.6.local.yml" and pass arguments as "$@" so
spaces/globs are preserved; update the lines referencing COOLIFY_DIR and the
docker compose command accordingly.
In `@coolify/docker-compose.5.yml`:
- Around line 24-28: The healthcheck currently uses the placeholder command
shown in the healthcheck -> test -> CMD -> echo ok, which always returns
success; replace that test with a real HTTP probe against Ghost (e.g., curl
--fail http://localhost:2368/ or the admin endpoint /ghost/) and add sensible
timing settings. Update the healthcheck.test to use CMD curl --fail --silent
--show-error http://localhost:2368/ || exit 1 (or wget --spider), and add
healthcheck.interval, healthcheck.timeout, and healthcheck.retries entries to
ensure the container is marked unhealthy on real failures.
In `@coolify/docker-compose.6.yml`:
- Around line 58-64: The mail__from setting is currently optional and can be
empty, so update the docker-compose variable expansion for mail__from to make
MAIL_FROM required (e.g., use a required expansion pattern) and/or add an
explicit validation/documentation step so deployments fail fast when MAIL_FROM
is unset; target the mail__from key and related env var names (MAIL_FROM,
mail__options__auth__user, mail__options__auth__pass) and ensure the compose
config enforces MAIL_FROM presence or the README/startup checks clearly mark
MAIL_FROM as mandatory for production admin email flows.
- Line 2: The compose file defines a service named "wizard" but other services
reference "ghost-gate", breaking the dependency graph; update the service name
to match the dependents or update the dependents to match this service
consistently—either rename the top-level service "wizard" to "ghost-gate" or
replace all references to "ghost-gate" in depends_on/depends sections with
"wizard" (check occurrences near the other mentioned locations) so the service
name used by Docker Compose matches across the file.
In `@coolify/README.md`:
- Around line 13-27: Add a short "Production checklist" note under the "New
Install" section that explicitly lists required SMTP/mail environment variables
(at minimum MAIL_FROM plus SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD or
equivalent auth vars) and instructs users to set them in the Environment before
final deploy to ensure admin/system emails work; apply the same checklist note
to the other deployment/production guidance block already present elsewhere in
the README so both locations warn about email config prior to go‑live.
- Around line 38-40: The shell commands that run `rm -rf
${NEWID}_ghost-content-data; cp -r ${OLDID}_ghost-content-data
${NEWID}_ghost-content-data` are unsafe if NEWID or OLDID are empty or
identical; add explicit validation before any destructive action: check that
NEWID and OLDID are non-empty, not equal, and that the source directories
(referenced by ${OLDID}_ghost-content-data and ${OLDID}_ghost-mysql-data) exist
and are directories; fail fast with a clear error message if any check fails (do
not proceed to rm/cp), and ensure all variable expansions are quoted to avoid
globbing/word-splitting and consider adding a dry-run or confirmation step
before performing the rm -rf on the target directories.
In `@ghost-gate/Dockerfile`:
- Around line 20-23: Replace the unsafe "curl -sSf https://tinybird.co | sh"
invocation in the Dockerfile with a verified installation method: either pull
the official Tinybird CLI image (e.g., use the
tinybirdco/tinybird-cli-docker:6.3.0@sha256:<digest> image and run the CLI from
that image) or install the PyPI package (add tinybird-cli==6.4.1 to
requirements.txt or run pip install tinybird-cli==6.4.1 during build); if you
switch to pip, remove only curl after installation (adjust the apt-get purge of
curl around the new pip install step) and keep the apt-get autoremove and rm -rf
/var/lib/apt/lists/* cleanup.
In `@ghost-gate/entrypoint.sh`:
- Line 5: Add an explicit chmod 0700 after the mkdir -p /home/tinybird call so
the secrets directory always has restrictive permissions even if it already
exists; update the entrypoint.sh sequence that currently contains "mkdir -p
/home/tinybird" to follow with a "chmod 0700 /home/tinybird" (ensure this runs
unconditionally and before any secrets are written).
In `@ghost-gate/server.js`:
- Around line 630-632: After creating the directory and writing the TINYB_HOME
file, explicitly enforce file permissions by calling fs.chmod on TINYB_HOME with
mode 0o600; update the block around fs.mkdir and fs.writeFile (references:
fs.mkdir, fs.writeFile, TINYB_HOME) to perform await fs.chmod(TINYB_HOME, 0o600)
immediately after the write (and propagate or handle any error) so existing
files get their permissions corrected regardless of writeFile's mode option.
- Around line 567-873: The setup endpoints mounted on gateRouter (routes like
POST /api/tinybird/lookup, /generate, /auth, /deploy, GET /api/tinybird/env and
GET /tinybird_setup via setupPage) are unprotected; add a setup auth middleware
and apply it to gateRouter before the existing route definitions (e.g.,
gateRouter.use(setupAuthMiddleware)). Implement a small function
(setupAuthMiddleware or checkSetupAuth) that validates a one-time setup token or
Basic Auth header against a stored secret (persisted or derived at startup) and
returns 401 for unauthorized requests; ensure the middleware allows requests to
continue when valid and that any one-time token is invalidated after successful
use if desired. Also ensure PROXY_MODE/isTinybirdReady logic still mounts
gateRouter only in setup mode and that error responses remain consistent (use
existing res.status(...) patterns).
In `@mysql-init/ensure-activitypub.sh`:
- Around line 22-30: The SQL here-doc is vulnerable because MYSQL_USER and
MYSQL_PASSWORD are embedded raw into single-quoted SQL literals (the printf
block in ensure-activitypub.sh), so passwords containing ' will break the SQL;
fix by escaping single quotes in both MYSQL_USER and MYSQL_PASSWORD before using
them in the printf (e.g., transform each ' -> '\''), use the escaped variables
in the printf that builds the CREATE USER / IDENTIFIED BY / ALTER USER / GRANT
statements, and apply the identical change to mysql-init/mysql-app-bootstrap.sh
for the same printf/SQL block.
In `@mysql-init/mysql-app-bootstrap.sh`:
- Around line 23-37: The script injects MYSQL_USER, MYSQL_PASSWORD and entries
from MYSQL_MULTIPLE_DATABASES directly into SQL (CREATE USER/ALTER USER/CREATE
DATABASE/GRANT), which breaks on passwords containing single quotes and allows
injection; fix by validating database/user identifiers against a safe regex
(e.g. only [A-Za-z0-9_]-style characters) and skipping any that don't match, and
escape SQL string literals (replace each single-quote in MYSQL_PASSWORD with
'\'' or use a helper that safely quotes literals) before interpolating into the
printf that feeds mysql; also apply the same validation/escaping when iterating
MYSQL_MULTIPLE_DATABASES and only pass the sanitized names to the mysql -h
127.0.0.1 -uroot -p"${MYSQL_ROOT_PASSWORD}" commands.
In `@README.md`:
- Around line 20-22: Replace the personal fork URL with the canonical repository
in the README: locate the occurrences of the string
"https://github.com/BadPirate/ghost-docker.git" (used in the "As Public
Repository" / deployment steps and the repeated block around lines referencing
Docker Compose) and change them to
"https://github.com/TryGhost/ghost-docker.git"; ensure both instances (the one
after "As Public Repository" and the duplicate near "Docker Compose Location")
are updated so all deployment instructions point to TryGhost/ghost-docker.
- Around line 5-10: The three entries using bare bracketed filenames
(docker-compose.5.yml, docker-compose.6.local.yml, docker-compose.6.yml) are not
valid Markdown links; update those list items to either use inline code spans
(e.g., `docker-compose.5.yml`) or real Markdown links pointing to the file/URL
(e.g., [docker-compose.5.yml](./coolify/docker-compose.5.yml)) so they render
correctly; edit the three list lines (the entries referencing
docker-compose.5.yml, docker-compose.6.local.yml, and docker-compose.6.yml) to
apply the chosen format consistently and preserve the existing explanatory text.
- Line 7: The docker compose command in the README uses subcommand-first option
order and will fail with Compose V2; update the command string that currently
reads "docker compose up -f coolify/docker-compose.6.yml -f
coolify/docker-compose.6.local.yml" so that the global -f flags precede the
subcommand, e.g. reorder to "docker compose -f coolify/docker-compose.6.yml -f
coolify/docker-compose.6.local.yml up" wherever that command appears in the
README (the diff line with the compose command).
In `@tinybird/tb-wrapper`:
- Line 12: Update the echo message in the tinybird/tb-wrapper script to remove
the duplicated word "wizard": change the user-facing string in the echo call
that currently reads "Paste auth JSON into the wizard wizard or run: tb login
--method code" to "Paste auth JSON into the wizard or run: tb login --method
code" so the instruction is correct and clear.
In `@wizard/Dockerfile`:
- Around line 17-18: The Dockerfile currently runs an unverified remote
installer via "RUN curl -sSf https://tinybird.co | sh" and then sets ENV PATH;
replace that insecure installer by either switching the base or adding a
reproducible install: (A) use Tinybird's official Docker image (FROM
tinybirdco/tinybird-cli-docker@sha256:<digest> or a specific version tag) so the
CLI is provided immutably, or (B) install the pip package reproducibly by adding
tinybird-cli==<version> to your requirements.txt and running pip install -r
requirements.txt (and keep or adjust ENV PATH="/root/.local/bin:${PATH}" if
needed); remove the curl pipe, pin the version or SHA256 digest, and ensure the
CLI binary is available on PATH after the change.
In `@wizard/server.js`:
- Around line 580-583: The wizard setup routes are exposed without auth via
wizardRouter (created with express.Router()) and thus leak Tinybird credentials
and deployment state; before mounting wizardRouter, add and apply an
authentication middleware (e.g., verifyAdminToken/requireSetupAuth) to
wizardRouter.use(...) so every setup endpoint is token-gated, and ensure the
middleware checks a configured secret/session and rejects unauthorized requests;
update the mount point where wizardRouter is attached so only authenticated
requests reach routes that read/mutate Tinybird credentials or deployment state.
- Around line 652-664: The deploy endpoint handler (wizardRouter.post
'/api/tinybird/deploy') currently forwards the user-controlled userApiUrl into
resolveApiHostForDeploy and ultimately to deployWithUiToken, allowing arbitrary
outbound hosts; fix it by enforcing an allowlist of permitted Tinybird hostnames
(or exact host+port entries) and validating the parsed host from userApiUrl
against that list before calling resolveApiHostForDeploy or deployWithUiToken;
if the host is not on the allowlist, return a 400 error (or fallback to the
default Tinybird host) and do not proceed with deployment. Ensure validation
logic is applied to userApiUrl (and its parsed hostname) and reference the
symbols userApiUrl, resolveApiHostForDeploy, and deployWithUiToken when updating
the handler.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: a1db94fd-c731-4545-8616-104f9b96451b
⛔ Files ignored due to path filters (2)
ghost-gate/package-lock.jsonis excluded by!**/package-lock.jsonwizard/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (24)
.dockerignoreCLAUDE.mdREADME.mdcompose.ymlcoolify/README.mdcoolify/deploy_local.shcoolify/docker-compose.5.ymlcoolify/docker-compose.6.local.ymlcoolify/docker-compose.6.ymlghost-gate/.dockerignoreghost-gate/Dockerfileghost-gate/entrypoint.shghost-gate/package.jsonghost-gate/server.jsmysql-init/docker-entrypoint-shim.shmysql-init/ensure-activitypub.shmysql-init/mysql-app-bootstrap.shmysql/Dockerfiletinybird/tb-wrapperwizard/.dockerignorewizard/Dockerfilewizard/entrypoint.shwizard/package.jsonwizard/server.js
| COOLIFY_DIR=$(dirname $0) | ||
|
|
||
| docker compose --project-directory $COOLIFY_DIR/.. -f $COOLIFY_DIR/docker-compose.6.yml -f $COOLIFY_DIR/docker-compose.6.local.yml up $@ |
There was a problem hiding this comment.
Quote path/arg expansions to avoid shell splitting bugs.
$0, $COOLIFY_DIR, and $@ should be quoted; otherwise paths/args with spaces or globs are mishandled.
Suggested patch
#!/usr/bin/env bash
+set -euo pipefail
-COOLIFY_DIR=$(dirname $0)
+COOLIFY_DIR="$(dirname "$0")"
-docker compose --project-directory $COOLIFY_DIR/.. -f $COOLIFY_DIR/docker-compose.6.yml -f $COOLIFY_DIR/docker-compose.6.local.yml up $@
+docker compose \
+ --project-directory "$COOLIFY_DIR/.." \
+ -f "$COOLIFY_DIR/docker-compose.6.yml" \
+ -f "$COOLIFY_DIR/docker-compose.6.local.yml" \
+ up "$@"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@coolify/deploy_local.sh` around lines 3 - 5, The script fails to quote
path/arg expansions; change COOLIFY_DIR assignment to use dirname "$0" (e.g.,
COOLIFY_DIR=$(dirname "$0")) and quote usages in the docker compose invocation:
--project-directory "$COOLIFY_DIR/.." and -f "$COOLIFY_DIR/docker-compose.6.yml"
-f "$COOLIFY_DIR/docker-compose.6.local.yml" and pass arguments as "$@" so
spaces/globs are preserved; update the lines referencing COOLIFY_DIR and the
docker compose command accordingly.
| healthcheck: | ||
| test: | ||
| - CMD | ||
| - echo | ||
| - ok |
There was a problem hiding this comment.
Replace placeholder Ghost healthcheck with a real probe.
echo ok always passes, so unhealthy Ghost instances are still marked healthy.
Suggested patch
healthcheck:
test:
- - CMD
- - echo
- - ok
+ - CMD-SHELL
+ - wget -q -S -T 3 --spider http://127.0.0.1:2368/ 2>&1 | grep -q 'HTTP/'
interval: 5s
timeout: 20s
retries: 10🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@coolify/docker-compose.5.yml` around lines 24 - 28, The healthcheck currently
uses the placeholder command shown in the healthcheck -> test -> CMD -> echo ok,
which always returns success; replace that test with a real HTTP probe against
Ghost (e.g., curl --fail http://localhost:2368/ or the admin endpoint /ghost/)
and add sensible timing settings. Update the healthcheck.test to use CMD curl
--fail --silent --show-error http://localhost:2368/ || exit 1 (or wget
--spider), and add healthcheck.interval, healthcheck.timeout, and
healthcheck.retries entries to ensure the container is marked unhealthy on real
failures.
| mail__from: ${MAIL_FROM} | ||
| mail__options__auth__pass: ${MAIL_OPTIONS_AUTH_PASS} | ||
| mail__options__auth__user: ${MAIL_OPTIONS_AUTH_USER} | ||
| mail__options__secure: ${MAIL_OPTIONS_SECURE:-true} | ||
| mail__options__port: ${MAIL_OPTIONS_PORT:-465} | ||
| mail__options__service: ${MAIL_OPTIONS_SERVICE:-Mailgun} | ||
| mail__options__host: ${MAIL_OPTIONS_HOST} |
There was a problem hiding this comment.
Make MAIL_FROM explicit for production deployments.
mail__from is currently optional/empty. Add a required expansion (${MAIL_FROM:?...}) or document it as mandatory so admin/system email flows are not silently broken.
Based on learnings: "Email configuration is critical even without newsletter features as it is used for admin notifications".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@coolify/docker-compose.6.yml` around lines 58 - 64, The mail__from setting is
currently optional and can be empty, so update the docker-compose variable
expansion for mail__from to make MAIL_FROM required (e.g., use a required
expansion pattern) and/or add an explicit validation/documentation step so
deployments fail fast when MAIL_FROM is unset; target the mail__from key and
related env var names (MAIL_FROM, mail__options__auth__user,
mail__options__auth__pass) and ensure the compose config enforces MAIL_FROM
presence or the README/startup checks clearly mark MAIL_FROM as mandatory for
production admin email flows.
| ### New Install | ||
|
|
||
| Requirements: Coolify UI v4, host with at least 5GB disk space and 2GB RAM | ||
|
|
||
| Primary purpose was to make it easy to configure a new / existing ghost 6 instance to run from coolify, still requires tinybird at this time, but there is a useful setup tool built in (Wizard) which also functions as a gateway after configuration. | ||
|
|
||
| 1. In Coolify UI Project: Add Resource | ||
| 2. As Public Repository: https://github.com/BadPirate/ghost-docker.git | ||
| 3. Select Build Pack: "Docker Compose" | ||
| 4. Docker Compose Location: `/coolify/docker-compose.6.yml` | ||
| 5. General -> Domains for wizard: https://<your coolify domain>:3989 | ||
| 6. Deploy | ||
| 7. After deployment, visit (From links for ghost-gate or at the URL) and configure tinybird | ||
| 8. Stop deployment, paste the tinybird config variables into Environment section of your deployment and relaunch | ||
| 9. Profit! (?) |
There was a problem hiding this comment.
Document required SMTP/mail env vars before final deploy.
Please add a short “production checklist” note requiring MAIL_FROM and SMTP auth/host values before go-live; otherwise admin/system emails can silently fail after migration.
Based on learnings: "Email configuration is critical even without newsletter features as it is used for admin notifications".
Also applies to: 41-45
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@coolify/README.md` around lines 13 - 27, Add a short "Production checklist"
note under the "New Install" section that explicitly lists required SMTP/mail
environment variables (at minimum MAIL_FROM plus SMTP_HOST, SMTP_PORT,
SMTP_USER, SMTP_PASSWORD or equivalent auth vars) and instructs users to set
them in the Environment before final deploy to ensure admin/system emails work;
apply the same checklist note to the other deployment/production guidance block
already present elsewhere in the README so both locations warn about email
config prior to go‑live.
| 2. As Public Repository: https://github.com/BadPirate/ghost-docker.git | ||
| 3. Select Build Pack: "Docker Compose" | ||
| 4. Docker Compose Location: `/coolify/docker-compose.6.yml` |
There was a problem hiding this comment.
Use the canonical repository URL in deployment steps.
Line 20 and Line 32 currently point users to a personal fork (BadPirate/ghost-docker). For official docs, this should reference TryGhost/ghost-docker to avoid supply-chain and maintenance drift.
Also applies to: 32-34
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@README.md` around lines 20 - 22, Replace the personal fork URL with the
canonical repository in the README: locate the occurrences of the string
"https://github.com/BadPirate/ghost-docker.git" (used in the "As Public
Repository" / deployment steps and the repeated block around lines referencing
Docker Compose) and change them to
"https://github.com/TryGhost/ghost-docker.git"; ensure both instances (the one
after "As Public Repository" and the duplicate near "Docker Compose Location")
are updated so all deployment instructions point to TryGhost/ghost-docker.
| echo "Copied .tinyb auth file to current directory" | ||
| else | ||
| echo "No .tinyb auth file found, please run 'docker compose run --rm tinybird-login' first to login" | ||
| echo "No .tinyb auth file found. Paste auth JSON into the wizard wizard or run: tb login --method code" |
There was a problem hiding this comment.
Fix duplicated word in the auth error message.
Line 12 has a typo (wizard wizard) in user-facing instructions.
Suggested fix
- echo "No .tinyb auth file found. Paste auth JSON into the wizard wizard or run: tb login --method code"
+ echo "No .tinyb auth file found. Paste auth JSON into the wizard or run: tb login --method code"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| echo "No .tinyb auth file found. Paste auth JSON into the wizard wizard or run: tb login --method code" | |
| echo "No .tinyb auth file found. Paste auth JSON into the wizard or run: tb login --method code" |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tinybird/tb-wrapper` at line 12, Update the echo message in the
tinybird/tb-wrapper script to remove the duplicated word "wizard": change the
user-facing string in the echo call that currently reads "Paste auth JSON into
the wizard wizard or run: tb login --method code" to "Paste auth JSON into the
wizard or run: tb login --method code" so the instruction is correct and clear.
| RUN curl -sSf https://tinybird.co | sh | ||
| ENV PATH="/root/.local/bin:${PATH}" |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Tinybird CLI Docker installation best practice: version-pinned installer artifact + checksum/signature verification steps.
💡 Result:
To install the Tinybird CLI using Docker with best practices for security and reproducibility, you should use a specific version tag and verify the image using its immutable SHA256 digest [1][2]. Best Practice Installation Steps: 1. Identify the desired version: Visit the official Docker Hub repository for tinybirdco/tinybird-cli-docker to identify the specific version tag you wish to use (e.g., 6.3.0) [1][2]. 2. Retrieve the immutable digest: On the Docker Hub tags page, locate the specific digest associated with your chosen version tag [2]. Using the digest ensures that you are running the exact, verified build of that version, protecting against tag-mutability attacks where a tag might be overwritten [2]. 3. Pull and verify the image: Use the digest to pull the image, which guarantees the integrity of the artifact: docker pull tinybirdco/tinybird-cli-docker@sha256: Replace with the actual SHA256 hash found on the Docker Hub tags page for your target version [2]. While Tinybird does not currently provide separate GPG signature files for their Docker images, using the immutable SHA256 digest provided by the official registry is the industry-standard method for verifying container image integrity and ensuring that the artifact has not been tampered with after publication [1][2]. Always avoid using the latest tag in production or CI/CD pipelines to prevent unexpected updates [2].
Citations:
- 1: https://hub.docker.com/r/tinybirdco/tinybird-cli-docker
- 2: https://hub.docker.com/r/tinybirdco/tinybird-cli-docker/tags
🌐 Web query:
Tinybird CLI installation script bash curl documentation checksums
💡 Result:
Tinybird CLI installation (official docs) uses an install script downloaded with curl, but the CLI docs pages shown in search results don’t include checksum values. 1) Install via the official curl|sh script (Tinybird Forward CLI docs) - Run: curl https://tinybird.co | sh This appears in the Tinybird “coding agents” quick-start as the “official install script” for Tinybird CLI [1]. - On Windows, the same source shows running an equivalent PowerShell command: powershell -ExecutionPolicy ByPass -c "irm https://tinybird.co | iex" [1]. 2) Alternative: use the Tinybird “quickstart” flow with tb init - Tinybird’s CLI quickstart then suggests using tb init after setting up the project directory (and notes both Classic and Forward CLIs use the tb command; you can check with which tb to avoid conflicts) [2]. 3) Where checksums are (or aren’t) documented - In the search results captured from Tinybird’s CLI documentation/quickstarts above, there is no mention of checksum verification for the curl|sh installer script (no SHA256/PGP/etc. shown in the cited snippets) [1][2]. If you want, tell me which OS you’re targeting (macOS/Linux/Windows) and whether you want to verify the installer with a checksum; I can do a follow-up targeted search specifically for “sha256”, “checksum”, or “gpg” in Tinybird CLI install documentation and any linked release/signature pages.
Citations:
- 1: https://www.tinybird.co/docs/forward/get-started/quick-start-coding-agents
- 2: https://www.tinybird.co/docs/forward/quickstarts/cli
🌐 Web query:
Tinybird CLI official installation methods version pinned
💡 Result:
Official Tinybird CLI installation methods (and how to pin a version) 1) Install the Python package on your machine (pip) - Tinybird CLI is provided as the Python package tinybird-cli. The docs recommend using a virtual environment and then running pip install tinybird-cli [1]. - To update it, run pip install --upgrade tinybird-cli [1]. 2) Use the official Docker image - Tinybird provides an official Docker image tinybirdco/tinybird-cli-docker that contains a Tinybird CLI executable ready to use [1]. - The Docker image can be run from the terminal via docker run ... -it tinybirdco/tinybird-cli-docker [1]. - To pin a specific CLI version with Docker, pull and use a specific image tag (for example ...:6.3.0) instead of ...:latest [2]. 3) Pin the CLI version in generated projects (requirements.txt) - Tinybird’s tb init --git can generate a requirements.txt that pins the tinybird-cli version range. - In the tinybird-cli v4.1.1 release notes, Tinybird states: “Fixed tb init --git to pin tinybird-cli>=4,<5 in requirements.txt to avoid issues with the latest version of the CLI.” [3] Practical pinning recipes - Pip: install a specific version directly (e.g., pip install tinybird-cli==<version>) after creating a venv; the base install method is documented as pip install tinybird-cli [1]. - Docker: use a specific tag from the official image instead of latest [2]. - Project scaffolding: run tb init --git so the project’s requirements.txt includes a tinybird-cli version constraint [3].
Citations:
- 1: https://www.tinybird.co/docs/classic/cli/install
- 2: https://www.tinybird.co/docs/forward/quickstarts/cli
- 3: https://www.tinybird.co/blog/tinybird-cli-now-supports-windows
Use Tinybird's official Docker image or pip package instead of piping an unverified installer script.
The curl -sSf https://tinybird.co | sh method executes unverified remote code without integrity checks. Tinybird provides official alternatives that support version pinning and integrity verification:
- Docker image: Use
tinybirdco/tinybird-cli-docker@sha256:<digest>with a specific version tag and immutable SHA256 digest - pip package: Use
tinybird-cli==<version>in a requirements file for reproducible builds with explicit version control
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@wizard/Dockerfile` around lines 17 - 18, The Dockerfile currently runs an
unverified remote installer via "RUN curl -sSf https://tinybird.co | sh" and
then sets ENV PATH; replace that insecure installer by either switching the base
or adding a reproducible install: (A) use Tinybird's official Docker image (FROM
tinybirdco/tinybird-cli-docker@sha256:<digest> or a specific version tag) so the
CLI is provided immutably, or (B) install the pip package reproducibly by adding
tinybird-cli==<version> to your requirements.txt and running pip install -r
requirements.txt (and keep or adjust ENV PATH="/root/.local/bin:${PATH}" if
needed); remove the curl pipe, pin the version or SHA256 digest, and ensure the
CLI binary is available on PATH after the change.
| // --- Wizard setup UI / API (only mounted when setup is incomplete) ----------------- | ||
| const wizardRouter = express.Router(); | ||
| wizardRouter.use(express.json({ limit: '256kb' })); | ||
|
|
There was a problem hiding this comment.
Protect setup routes with authentication before exposing token workflows.
Line 882 mounts wizardRouter without auth, while Lines 593–725 expose endpoints that can return or mutate Tinybird credentials and deployment state. In setup mode, any reachable client can hit these routes.
🔐 Suggested hardening (token-gated setup mode)
+const WIZARD_SETUP_TOKEN = String(process.env.WIZARD_SETUP_TOKEN || '').trim();
+
+function requireWizardAuth(req, res, next) {
+ // Keep health endpoint ungated for orchestrator checks
+ if (req.path === '/__wizard/health') return next();
+ const supplied = String(req.get('x-wizard-token') || '').trim();
+ if (!WIZARD_SETUP_TOKEN || supplied !== WIZARD_SETUP_TOKEN) {
+ return res.status(401).json({ ok: false, error: 'Unauthorized setup access' });
+ }
+ return next();
+}
...
if (PROXY_MODE) {
...
} else {
+ app.use(requireWizardAuth);
app.use(wizardRouter);
app.get('/', (_req, res) => res.redirect(302, '/tinybird_setup'));
app.use((_req, res) => res.redirect(302, '/tinybird_setup'));
}Also applies to: 881-886
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@wizard/server.js` around lines 580 - 583, The wizard setup routes are exposed
without auth via wizardRouter (created with express.Router()) and thus leak
Tinybird credentials and deployment state; before mounting wizardRouter, add and
apply an authentication middleware (e.g., verifyAdminToken/requireSetupAuth) to
wizardRouter.use(...) so every setup endpoint is token-gated, and ensure the
middleware checks a configured secret/session and rejects unauthorized requests;
update the mount point where wizardRouter is attached so only authenticated
requests reach routes that read/mutate Tinybird credentials or deployment state.
| wizardRouter.post('/api/tinybird/deploy', async (req, res) => { | ||
| const userApiUrl = | ||
| typeof req.body?.apiUrl === 'string' ? req.body.apiUrl.trim() : ''; | ||
| const adminToken = | ||
| typeof req.body?.adminToken === 'string' ? req.body.adminToken.trim() : ''; | ||
|
|
||
| if (adminToken) { | ||
| try { | ||
| const { resolved, tokenHost, note } = resolveApiHostForDeploy( | ||
| userApiUrl, | ||
| adminToken | ||
| ); | ||
| const { stdout, stderr } = await deployWithUiToken(resolved, adminToken); |
There was a problem hiding this comment.
Validate apiUrl to prevent arbitrary outbound host usage from deploy endpoint.
Line 664 passes a user-controlled apiUrl into tb --host without allowlisting. This lets callers route deploy traffic to non-Tinybird hosts.
🛡️ Suggested host allowlist for deploy host
+function isAllowedTinybirdApiUrl(raw) {
+ try {
+ const u = new URL(String(raw || '').trim());
+ if (u.protocol !== 'https:') return false;
+ return u.hostname === 'api.tinybird.co' || u.hostname.endsWith('.tinybird.co');
+ } catch {
+ return false;
+ }
+}
...
wizardRouter.post('/api/tinybird/deploy', async (req, res) => {
const userApiUrl =
typeof req.body?.apiUrl === 'string' ? req.body.apiUrl.trim() : '';
const adminToken =
typeof req.body?.adminToken === 'string' ? req.body.adminToken.trim() : '';
if (adminToken) {
+ if (userApiUrl && !isAllowedTinybirdApiUrl(userApiUrl)) {
+ return res.status(400).json({
+ ok: false,
+ mode: 'ui',
+ error: 'apiUrl must be a valid Tinybird HTTPS API endpoint.',
+ });
+ }
try {
const { resolved, tokenHost, note } = resolveApiHostForDeploy(
userApiUrl,
adminToken
);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@wizard/server.js` around lines 652 - 664, The deploy endpoint handler
(wizardRouter.post '/api/tinybird/deploy') currently forwards the
user-controlled userApiUrl into resolveApiHostForDeploy and ultimately to
deployWithUiToken, allowing arbitrary outbound hosts; fix it by enforcing an
allowlist of permitted Tinybird hostnames (or exact host+port entries) and
validating the parsed host from userApiUrl against that list before calling
resolveApiHostForDeploy or deployWithUiToken; if the host is not on the
allowlist, return a 400 error (or fallback to the default Tinybird host) and do
not proceed with deployment. Ensure validation logic is applied to userApiUrl
(and its parsed hostname) and reference the symbols userApiUrl,
resolveApiHostForDeploy, and deployWithUiToken when updating the handler.
Adds a Coolify-targeted Ghost 6 stack that is fronted by a new
`ghost-gate` service. ghost-gate is the only publicly-exposed container
and serves two purposes:
- On first launch it runs the MySQL app-user / GRANT / CREATE DATABASE
bootstrap against the stock mysql:8 image (no init scripts, no
sidecar), then serves a Tinybird setup wizard on every path. The
operator pastes a workspace admin token, the wizard resolves the
region + tokens via Tinybird's Token API (and publishes the Ghost
analytics schema if the tracker token is missing), and prints the
four TINYBIRD_* env vars to copy into Coolify.
- After the TINYBIRD_* vars are set and the service is redeployed,
ghost-gate switches to reverse-proxy mode:
/.ghost/analytics/* -> traffic-analytics:3000 (prefix stripped)
/.well-known/webfinger -> activitypub:8080
/.well-known/nodeinfo -> activitypub:8080
/.ghost/activitypub/* -> activitypub:8080
everything else -> ghost:2368
Same-origin tracker posts remove the need for CORS handling or
Traefik label soup.
The healthcheck reports healthy as soon as the DB bootstrap finishes
so Coolify/Traefik will route ingress to the container on first
launch; proxy vs. setup mode is chosen from TINYBIRD_* env vars only.
Files:
- coolify/docker-compose.6.yml: Ghost 6 + MySQL + traffic-analytics +
ActivityPub + ghost-gate. No profiles (this stack is always analytics-
on); legacy caddy/tinybird_home volumes dropped. Image pins aligned
with the main compose (traffic-analytics 1.0.196, activitypub 1.2.2,
mysql 8.0.44 by digest).
- coolify/docker-compose.5.yml: legacy Ghost 5 template preserved for
migrations from the old built-in Coolify Ghost service, documented as
such in a header comment.
- coolify/README.md: Coolify setup steps, routing table, and migration
notes.
- ghost-gate/: Express + http-proxy-middleware server, Tinybird CLI,
bundled Ghost Tinybird datafiles, MIT-licensed package.json with a
real express 4.21.x pin and a fresh lockfile. Dockerfile uses wget at
runtime for the healthcheck; curl is installed transiently for the
Tinybird installer and purged from the final image.
- .dockerignore at repo root to keep local state out of build contexts.
Made-with: Cursor
I used Coolify for my Ghost Docker configuration with the built in template for Ghost 5. But like others I wanted Ghost 6 support.
In the process, I ended up creating an alternative docker compose config, as well as a setup / migration wizard to make it easy to migrate from an existing Coolify Ghost-5 install.
The
gate-wizardis a lightweight express router that detects if TINYBIRD is configured in environment variables, and if not, provides a web interface in place of ghost to get the configuration setup properly:Simplifying the tinybird setup flow, and making it possible / easier to do as the existing procedure makes the assumption that your old ghost install is running on local machine (which it won't be for coolify).
Tested with a migration and a new install (both documented in [coolify/README.md].
After install wizard serves as gateway to prevent CORS issues with tinybird and provide same domain communication, in place of existing router image.
Next steps