Skip to content

Repository files navigation

meith-board

A forum, built on Meith.

Deploy

Three paths onto a server, all ending at the same /install. Quick start onto Coolify is the default and needs nothing but a push; advanced/prebuilt moves the build off the server, onto GitHub Actions, for a low-spec build server or a faster deploy; without a panel is the same four containers run by hand, with your own .env and reverse proxy, and no Coolify at all. Pick one — a board only ever runs one of them at a time.

Quick start (default)

Coolify builds the image itself, from this repository, every time it deploys — there is nothing to push anywhere first and no image tag to paste in. Two steps:

  1. Push this repository to GitHub.

  2. Point Coolify 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. Nothing else to set: docker-compose.yaml builds web and migrate from Dockerfile itself, so there is no MEITH_IMAGE here at all.

  3. Deploy, then /install on your own domain. Coolify issues the certificate; the installer from there is the one docs/getting-started/deployment/coolify.md walks through, screen for screen. It seals itself when it finishes, and /install answers 404 from then on — run it against the database you are going to keep. Every push to main after this is picked up the next time Coolify's own Redeploy button runs — pushing alone does not rebuild it.

The trade for that zero setup is a heavier build: Dockerfile installs this board's full dependency closure on the server itself, on every deploy, rather than starting from a warm base image. A 2 GB VPS can OOM on it. If that is your server, use the advanced path below instead.

A quick-start board never needs Dockerfile.prebuilt, docker-compose.prebuilt.yaml or .github/workflows/build.yml — delete all three.

Advanced / prebuilt — for a low-spec server or a faster deploy

Something else builds the image ahead of time; the server only ever pulls one. Three steps, nothing to configure by hand beyond one value only you know:

  1. Push this repository to GitHub. .github/workflows/build.yml builds Dockerfile.prebuilt on every push to main and pushes the result to your own GitHub Container Registry, ghcr.io/<you>/meith-board — using only the GITHUB_TOKEN every GitHub Actions run already carries. No secret to add, no registry account beyond the GitHub account you already have.

    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 at docker-compose.prebuilt.yaml — a Public Git repository resource with Docker Compose as its build pack, this repository as its source, and its Compose file field changed from Coolify's default of docker-compose.yaml to docker-compose.prebuilt.yaml. That file 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.prebuilt.yaml refuses to start without it, with a message saying why). ghcr.io/<you>/meith-board:${{ github.sha }} names that one build and nothing else, ever; ghcr.io/<you>/meith-board:latest follows main instead, so installing a plugin later is a push and a Redeploy — the trade this path takes, at the cost of an unrelated redeploy pulling whatever main most recently built.

  3. Deploy, then /install on your own domain. Same installer, same docs/getting-started/deployment/coolify.md walk-through, same one-time seal. Every push to main after this rebuilds the image; Coolify's own Redeploy button is what actually pulls it — pushing alone does not.

No Docker Hub, no paid CI: GitHub Actions' free tier and GHCR are the whole 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.prebuilt.yaml's MEITH_IMAGE can reach.

docker build -f Dockerfile.prebuilt --build-arg MEITH_VERSION=$(node -p "require('./package.json').dependencies['@meith/web']") -t meith-board .

Without a panel

docker-compose.byhand.yaml, beside the two Coolify files above, is the same four containers deployed with nothing generating secrets for you: a .env you write yourself, a port published for the reverse proxy you already run, and docker compose up -d --build in place of a panel's Deploy button. docs/getting-started/deployment/docker-compose.md is the full walkthrough this file is the last step of, including the .env this repository does not carry — nothing here belongs in git. Delete this file if you know you will only ever deploy through Coolify; keep it, and it needs nothing else changed, if you later want to move away from Coolify without changing how the board itself is built.

Two things nothing configures for you, on any path:

  • 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. The compose file'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 route (or run meith task:run) every minute, or nothing catches up and nothing errors.

Local

npm install
npm run dev

No environment file, no database: with no DATABASE_URL the board serves deterministic in-memory sample data, which is enough to click through every reading surface.

Posting needs Postgres. Copy .env.example to .env.local, set DATABASE_URL and the two secrets in it, then:

npm run meith -- migrate
echo "<password>" | npm run meith -- user:create --username <name> --email <address> --group administrators

Configuring

  • meith.config.ts — installed themes and plugins. Everything installable is named here so the bundler can see it; nothing is found by scanning a directory at runtime.
  • /admin — settings, forums, groups, members, themes, maintenance. An administrator re-enters their password to get in, and again for anything destructive.
  • npm run meith -- --help — the operator CLI. Everything the panel does and a few things it cannot, without a browser.

Installing plugins and themes

Nothing installs into a running container — a plugin or theme has to be built into the image. In this repository:

  1. Add it. A plugin is one command, which installs the package and registers it:

    npm run meith -- plugin:add @meith/plugin-dues

    It writes board.plugins.json and regenerates meith.plugins.ts for you (npm run meith -- plugin:remove <key> reverses it). A theme is npm install --save-exact @meith/theme-midnight, then an entry in meith.config.ts's themes map following the shape of the default one already there — set defaultTheme to its key to make it the board's default.

  2. Commit and push, then Redeploy from Coolify — pushing alone does not rebuild. Quick start builds the new image on that redeploy; advanced/prebuilt waits for .github/workflows/build.yml to finish first, and Redeploy is what actually pulls the result.

  3. If it ships database changes, apply them once it is up — from Admin → System (Version & migrations) in the browser, or:

    docker compose run --rm web meith upgrade

See Installing plugins and themes for the full guide.

Upgrading

.github/workflows/update.yml does this for you: once a week — and whenever you press Run workflow on the Actions tab — it checks for a new Meith release and opens a pull request that moves every @meith/* package and next together, and rewrites the deploy files this scaffold owns (Dockerfile, the compose files, the workflows) to the new release's shape. A file you have edited yourself is never rewritten; the run's log names any it left for you. One-time setup: under Settings → Actions → General, enable Allow GitHub Actions to create and approve pull requests, or the workflow cannot open one.

Merging that pull request is still an upgrade, not a formality: read the release notes it links, take a backup first, and press Redeploy in Coolify after the merge — pushing alone does not rebuild. Once the new version serves, run npm run meith -- upgrade against it for the plugin migrations.

The same update, by hand and without waiting for the schedule:

npx create-meith@latest update
git commit -am "Update Meith"
git push

Under the hood, the version move is these two commands, plus the deploy-file rewrite neither of them can do:

npm install --save-exact @meith/web@latest @meith/cli@latest @meith/theme-default@latest
npm install --save-exact next@$(node -p "require('./node_modules/@meith/web/package.json').dependencies.next")

The second command is not optional. This board pins next itself, and the npm commands alone never bump it: upgrading only the @meith/* packages leaves the board's own pin on the old Next while @meith/web depends on the new one, which npm resolves by installing both — the build then runs on one version while everything reading package.json sees the other. Reading the version out of the freshly installed @meith/web is what keeps the two the same without anybody having to know the number.

next and @meith/web move together or not at all, which is why one updater owns the whole move and no dependency bot bumps either on its own. What Dependabot does keep current is this repository's own GitHub Actions — .github/dependabot.yml opens a weekly pull request bumping the actions pinned under .github/workflows, a safe, independent update the updater leaves to it.

On the quick-start path there is no version to keep in sync by hand: Dockerfile runs npm install straight from this package.json on every build, so a rebuild always picks up whatever is pinned there. On the advanced/prebuilt path, that package.json change is the whole pin: Dockerfile.prebuilt's own FROM line takes the version as a build argument, and .github/workflows/build.yml reads it straight out of package.json's own @meith/web dependency when it rebuilds — nothing in Dockerfile.prebuilt itself to keep in sync by hand. --save-exact matters either way: npm's default save-prefix is ^, and a caret range is not a legal Docker image tag for the advanced path — without it, this exact command would write "^0.18.0" and the next Dockerfile.prebuilt build would fail with invalid reference format instead of building. This project's own .npmrc sets save-exact=true for the same reason, so an npm install of anything else here — a plugin, say — stays pinned too; the build workflow also refuses to build from anything but an exact version, as a second line of defense. Once the rebuilt image is deployed, run npm run meith -- upgrade against it for the plugin migrations — see the operator CLI for running it against this deployment.

Migrations are forward-only. Recovery is by restore, so take a backup first — there is no down migration to undo a destructive one, and a button that pretended otherwise would be worse than its absence.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages