Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/cli/src/board-eject.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ describe('boardEject', () => {
// biome-ignore lint/suspicious/noTemplateCurlyInString: literal Dockerfile ARG syntax, not a template-string typo
'ghcr.io/meith-dev/meith-base:${MEITH_VERSION}',
)
await expect(readFile(join(target, 'docker-compose.yml'), 'utf8')).resolves.toContain(
await expect(readFile(join(target, 'docker-compose.yaml'), 'utf8')).resolves.toContain(
'services:',
)
await expect(readFile(join(target, '.github/workflows/build.yml'), 'utf8')).resolves.toContain(
Expand Down
85 changes: 47 additions & 38 deletions apps/web/public/create-board.sh
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ cat > "$BOARD_NAME/Dockerfile" <<'MEITH_SCAFFOLD_EOF'
# docs/contributing/development.md, "Consuming the board from a workspace") — it needs
# the full, un-pruned node_modules tree this board installed, not what Next
# traced as reachable from the web server alone. The tick itself is driven
# by docker-compose.yml's own `worker` service — a lightweight loop against
# by docker-compose.yaml's own `worker` service — a lightweight loop against
# /api/system/tick, not a compiled worker process, because @meith/worker is
# not published (see the meith repository's docs/contributing/release.md).
ARG MEITH_VERSION
Expand Down Expand Up @@ -305,7 +305,7 @@ cat > "$BOARD_NAME/docker-entrypoint.sh" <<'MEITH_SCAFFOLD_EOF'
#
# "web" (the default) runs the board; "migrate" applies the schema and
# exits. There is no "worker" role in this image: @meith/worker is not
# published, so nothing here can run it — docker-compose.yml's own `worker`
# published, so nothing here can run it — docker-compose.yaml's own `worker`
# service drives the tick a different way, calling this image's web role
# over HTTP instead of running as a role of this image.
set -e
Expand Down Expand Up @@ -411,27 +411,33 @@ jobs:
{
echo "## Deploy this image"
echo
echo "Paste this into the MEITH_IMAGE variable on the Coolify resource:"
echo "Either of these goes in the MEITH_IMAGE variable on the Coolify"
echo "resource:"
echo
echo " $IMAGE:${{ github.sha }}"
echo
echo "This tag names this run's build and nothing else, ever. $IMAGE:latest"
echo "also pushed, as a convenience for a quick manual pull; it moves on"
echo "every push to main, so any later Coolify redeploy — for any reason,"
echo "not necessarily this one — pulls whatever main most recently built,"
echo "including a commit still mid-feature. Prefer the sha above for the"
echo "value you actually set on the resource."
echo " $IMAGE:latest"
echo
echo "## One-time: make the package public"
echo "The first names this run's build and nothing else, ever: nothing"
echo "moves under you, and an upgrade is you editing this variable."
echo
echo "This package starts private. Coolify's pull fails until you visit"
echo "$PKG_URL and change its visibility — **Package settings** →"
echo "**Change visibility** → **Public**."
echo "The second follows main — every push rebuilds it and Coolify's next"
echo "Redeploy picks that build up, a commit still mid-feature included."
echo "That is the trade the quickstart takes while a board is young and"
echo "still gaining plugins: installing one is a push and a Redeploy, with"
echo "nothing on the resource to edit."
echo
echo "## One-time: check the package is public"
echo
echo "Coolify can only pull a public package, and this one may already be"
echo "one — a build from a public repository usually lands public. Open"
echo "$PKG_URL: if it does not already say Public, change it there —"
echo "**Package settings** → **Change visibility** → **Public**."
} >> "$GITHUB_STEP_SUMMARY"
MEITH_SCAFFOLD_EOF

mkdir -p "$(dirname -- "$BOARD_NAME/docker-compose.yml")"
cat > "$BOARD_NAME/docker-compose.yml" <<'MEITH_SCAFFOLD_EOF'
mkdir -p "$(dirname -- "$BOARD_NAME/docker-compose.yaml")"
cat > "$BOARD_NAME/docker-compose.yaml" <<'MEITH_SCAFFOLD_EOF'
# __MEITH_BOARD_NAME__, deployed by Coolify — the same shape as the meith repository's own
# docker/compose.coolify.yml: db, migrate, web, worker. See README.md for
# the three-step deploy story this file is the last step of.
Expand Down Expand Up @@ -549,7 +555,7 @@ A forum, built on [Meith](https://github.com/meith-dev/meith).
## Deploy

Nothing here builds on your own server — a 2 GB VPS OOMs on a Next.js build,
which is the whole reason `Dockerfile`, `docker-compose.yml` and
which is the whole reason `Dockerfile`, `docker-compose.yaml` and
`.github/workflows/build.yml` exist: something else builds the image, the
server only ever pulls one. Three steps, nothing to configure by hand beyond
one value only you know:
Expand All @@ -560,25 +566,28 @@ one value only you know:
`GITHUB_TOKEN` every GitHub Actions run already carries. No secret to
add, no registry account beyond the GitHub account you already have.

Open the run under the repository's **Actions** tab once it finishes —
its **Summary** prints the two things left: the exact image to paste
into step 2 below, and a direct link to the one-time step of making the
package public. It starts **private**, and Coolify's pull fails with an
authentication error no operator can act on until that is done.

2. **Point [Coolify](https://coolify.io) at `docker-compose.yml`** — a Docker
Compose resource, this repository as its source. `docker-compose.yml` already
carries Coolify's own "magic variables" for `AUTH_SECRET`,
`TICK_SECRET` and the database password, generated on the first deploy
and never typed in. The one thing Coolify cannot generate is the image
step 1 just pushed: set `MEITH_IMAGE` in the resource's own environment
to the value that run's Summary printed — `ghcr.io/<you>/__MEITH_BOARD_NAME__:${{ github.sha }}`,
a pin that only ever names that one build (`docker-compose.yml` refuses
to start without this set, with a message saying why). The same run also
pushes `ghcr.io/<you>/__MEITH_BOARD_NAME__:latest` as a convenience for a quick manual
pull, but it moves on every push to `main` — set it on the resource and a
later, unrelated redeploy can pull whatever `main` most recently built,
commit still mid-feature included.
That build is the thing step 2 waits on: open the repository's
**Actions** tab and let the run finish, because its **Summary** is where
the exact image to paste into step 2 comes from. The Summary also links
the package itself, to check it is public — a build from a public
repository usually lands public already, and a private one fails
Coolify's pull with an authentication error no operator can act on.

2. **Point [Coolify](https://coolify.io) at `docker-compose.yaml`** — a
**Public Git repository** resource with **Docker Compose** as its build
pack, this repository as its source. The name is Coolify's own default,
so its **Compose file** field is already right when the form opens, and
the file already carries Coolify's own "magic variables" for
`AUTH_SECRET`, `TICK_SECRET` and the database password, generated on
the first deploy and never typed in. The one thing Coolify cannot
generate is the image step 1 just pushed: set `MEITH_IMAGE` in the
resource's own environment to one of the two values that run's Summary
printed (`docker-compose.yaml` refuses to start without it, with a
message saying why). `ghcr.io/<you>/__MEITH_BOARD_NAME__:${{ github.sha }}` names
that one build and nothing else, ever; `ghcr.io/<you>/__MEITH_BOARD_NAME__:latest`
follows `main` instead, so installing a plugin later is a push and a
**Redeploy** — the trade the quickstart takes, at the cost of an
unrelated redeploy pulling whatever `main` most recently built.

3. **Deploy, then `/install` on your own domain.** Coolify issues the
certificate; the installer from there is the one
Expand All @@ -594,22 +603,22 @@ build side of this, for a board of any size.

**Building it yourself**: works on any machine with Docker, if you would
rather not use GitHub Actions for the build — push the result wherever
`docker-compose.yml`'s `MEITH_IMAGE` can reach.
`docker-compose.yaml`'s `MEITH_IMAGE` can reach.

```sh
docker build --build-arg MEITH_VERSION=$(node -p "require('./package.json').dependencies['@meith/web']") -t __MEITH_BOARD_NAME__ .
```

**Without a panel**: [docs/getting-started/deployment/docker-compose.md](https://github.com/meith-dev/meith/blob/main/docs/getting-started/deployment/docker-compose.md)
is the same four containers by hand — your own `.env`, a reverse proxy you
already run, no Coolify. `Dockerfile` and `docker-compose.yml` here are this
already run, no Coolify. `Dockerfile` and `docker-compose.yaml` here are this
board's own version of exactly that shape.

Two things nothing configures for you:

- **Mail.** Until `MAIL_DRIVER` and its three settings exist, every message is
written to the log and delivered to nobody, so password reset fails silently.
- **The tick.** `docker-compose.yml`'s `worker` service drives it here — a small
- **The tick.** `docker-compose.yaml`'s `worker` service drives it here — a small
loop calling `/api/system/tick` once a minute, since `@meith/web`'s own
worker package is not something a board outside the meith monorepo can
depend on yet. Deploy some other way and something still has to call that
Expand Down
9 changes: 5 additions & 4 deletions docs/customization/marketplace.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ where `mkdir` made it.
`my-board` becomes a complete workspace: `package.json` pinned to
*this image's exact release version* — never `latest`, so graduating is
never a surprise upgrade — the full deploy kit (`Dockerfile`,
`docker-compose.yml`, `.github/workflows/build.yml`, described in full in
`docker-compose.yaml`, `.github/workflows/build.yml`, described in full in
[Self-hosting § Custom boards](../getting-started/deployment/docker-compose.md#custom-boards)),
`board.plugins.json` matching what this build actually compiled in, and
`community.config.ts` matching the stock configuration. Every plugin the
Expand Down Expand Up @@ -331,9 +331,10 @@ branch is pushed to.

From here it is the same three-step deploy [Self-hosting § Custom
boards](../getting-started/deployment/docker-compose.md#custom-boards) describes for any scaffolded
board: make the GitHub package public (it starts private), point Coolify
at the new repository — it finds `docker-compose.yml` on its own — and
set `MEITH_IMAGE` to the image step 2 just pushed, then redeploy.
board: check the GitHub package is public, point Coolify at the new
repository — `docker-compose.yaml` is the path its **Compose file** field
already carries — and set `MEITH_IMAGE` to the image step 2 just pushed,
then redeploy.

### What does not move

Expand Down
83 changes: 51 additions & 32 deletions docs/getting-started/deployment/coolify.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,12 @@ All three end up in the same place: a repository on GitHub with the
deploy kit already in it.

- **Rather not open a terminal at all?** Click **Use this template** on
[meith-dev/template](https://github.com/meith-dev/template), name your
new repository, and skip straight to [step
3](#3-set-your-domain-and-deploy) — GitHub creates the repository and
its first commit for you, no local anything required.
[meith-dev/template](https://github.com/meith-dev/template) and name your
new repository — GitHub creates it and its first commit for you, no local
anything required, and there is nothing else to do in this step but its
last paragraph and the note under it. Skip the rest and read those: your
repository's first build has already started, and [step
3](#3-set-your-domain-and-deploy) needs what it prints.
- **Have a terminal, nothing installed?**

```sh
Expand All @@ -107,7 +109,7 @@ with no spaces.

The two commands write an identical small workspace into `./my-board` —
`package.json`, `community.config.ts`, and a deploy kit of its own:
`Dockerfile`, `docker-compose.yml` and `.github/workflows/build.yml` —
`Dockerfile`, `docker-compose.yaml` and `.github/workflows/build.yml` —
the same files the template repository already has. All three depend on
the published `@meith/web` and `@meith/cli` packages rather than
containing a copy of this repository, and this is what turns "installing
Expand Down Expand Up @@ -140,48 +142,65 @@ your computer, no registry account beyond the GitHub account you already
have.

> [!IMPORTANT]
> **Before moving on**, once that run finishes:
> **Before moving on, wait for that build.** Step 3 asks you for a value
> only the finished run knows, so there is no starting it early. Open your
> repository's **Actions** tab — **Build and push** is already running, or
> already done — and once it is green:
>
> 1. Open it, and click its **Summary** tab.
> 1. Open the run and read its **Summary**.
> 2. Copy the image value it shows — you'll paste it into Coolify in
> [step 3](#3-set-your-domain-and-deploy).
> 3. Click the link under **make the package public**, and do that now.
> It only takes a click, and skipping it means Coolify fails to pull
> the image later with a confusing error, so it's easier to handle
> here while you're already on the page.
> 3. Follow the Summary's link to the package itself and check it says
> **Public**. Often it already does — a build from a public repository
> usually publishes a public package — in which case there is nothing
> to do. If it says Private, change it here (**Package settings** →
> **Change visibility** → **Public**) while you are on the page:
> Coolify cannot pull a private package, and fails the deploy with an
> authentication error rather than an explanation.

## 3. Set your domain and deploy

In the panel: **New Resource → Docker Compose → Public Repository**.
In the panel: **New Resource → Public Git repository**. Paste your
repository's address into **Repository URL** and press **Check
repository** — Coolify reads the repository and opens **Build
configuration** underneath, where one field needs changing and the rest
are already right:

| Field | Value |
|---|---|
| Repository | `https://github.com/<you>/my-board` |
| Repository URL | `https://github.com/<you>/my-board` |
| Branch | `main` |
| Build pack | **Docker Compose** — the one field on this screen you have to change |
| Base directory | `/` |
| Compose file | `/docker-compose.yaml` — already correct; your repository's file is named for this default |

Coolify finds `docker-compose.yml` on its own — that is the one field
worth knowing you don't have to touch.
Then press **Continue**, which creates the resource.

Coolify offers a generated domain and accepts your own. Put yours in — the
one whose `A` record points at this server.

Before you press deploy, set one thing the compose file has no default
for: open the resource's **Environment Variables**, add `MEITH_IMAGE`,
and paste in the first value step 2's Actions run Summary printed —
`ghcr.io/<you>/my-board:latest`. The compose file refuses to start
without it.
Before you deploy, set the one thing the compose file has no default for:
open the resource's **Environment Variables**, add `MEITH_IMAGE`, and give
it the `:latest` value step 2's Actions run Summary printed —
`ghcr.io/<you>/my-board:latest`. The compose file refuses to start without
it.

> [!NOTE]
> **Why this matters later, not now.** That Summary also printed a second
> value ending in a long code instead of `:latest`. Using it instead is
> worth doing once the board is live and you care about upgrades happening
> only when you choose, not on their own — see [Self-hosting § Custom
> boards](./docker-compose.md#custom-boards) when you get there. For getting
> the board up today, the `:latest` value above is the right one; nothing
> below depends on which you picked.

Now press deploy. It pulls the image and takes a minute or two. Four
containers come up, in order:
> **Why `:latest`, and what the other value is for.** `:latest` follows your
> repository's `main` branch: installing a plugin later is a push and a
> **Redeploy**, with nothing on this screen to edit — which is the right
> trade while the board is young and you are still changing it. The Summary
> prints a second value beside it, the same image ending in a long commit
> code, which names that one build and nothing else, ever. Move `MEITH_IMAGE`
> to that once the board is settled and you want upgrades happening only when
> you choose, not on any redeploy — see [Self-hosting § Custom
> boards](./docker-compose.md#custom-boards) when you get there. Nothing below
> depends on which you picked.

Now deploy: the button is on the resource's own **Actions**, and it is the
step everything above was setting up, so leave it until the domain and
`MEITH_IMAGE` are both in. It pulls the image and takes a minute or two.
Four containers come up, in order:

| Container | What it does |
|---|---|
Expand Down Expand Up @@ -422,13 +441,13 @@ can fix and retry:
| What you see | What it is |
|---|---|
| The deploy fails before any container starts, complaining that `MEITH_IMAGE` is unset | You skipped setting it before deploying, or it is set on the wrong resource — [step 3](#3-set-your-domain-and-deploy). The compose file will not guess an image for you. |
| The deploy fails pulling the image, with an authentication error | The GHCR package is still private — [step 2](#2-create-your-board)'s note on making it public. |
| The deploy fails pulling the image, with an authentication error | The GHCR package is private — [step 2](#2-create-your-board)'s note on checking its visibility. |
| `migrate` exits non-zero | Read its log. A failed migration stops the stack on purpose rather than serving against a half-applied schema. |
| The `worker` container logs `tick failed` repeatedly | The board it is calling is not answering — check `web`'s own log first; the loop container has no logic of its own to break. |
| 413 on an upload | The proxy's body limit, not the board's. Raise it on the resource. |
| Password reset "sent" and never arrives | Mail is not configured, so the message is sitting in the web container's log. Check `/admin/settings?group=mail` and press the test button. |
| Nothing happens on a schedule | The `worker` container is not running, so nothing is calling `/api/system/tick` — see `/admin/system`. |
| The board is on a newer version than you deployed | `MEITH_IMAGE` is still on the mutable `:latest` tag, and a push to `main` since your last deploy — adding a plugin, say — landed a rebuild that **Restart** or **Redeploy** then picked up. Move `MEITH_IMAGE` to a commit-sha tag instead — [step 3](#3-set-your-domain-and-deploy) — and neither button can do it again on its own. |
| The board is on a newer version than you deployed | `MEITH_IMAGE` is on the `:latest` tag step 3 sets, working as intended: a push to `main` since your last deploy — adding a plugin, say — landed a rebuild that **Restart** or **Redeploy** then picked up. If you would rather that never happen unasked, move `MEITH_IMAGE` to the commit-sha value instead — [step 3](#3-set-your-domain-and-deploy) — and neither button can do it on its own. |

[Operations § Troubleshooting](../../guides/operations/operating.md#troubleshooting)
covers the failures that are about the board rather than the deploy.
Expand Down
Loading