Wekan
Open source kanban board application built with Meteor
Alternative to: trello
v10.43
2026-07-27v10.43 2026-07-28 WeKan ® release
This release adds the following new features:
A Docker Compose file for every FerretDB v1 backend, generated from one source. Thanks to xet7.
docker-compose.yml runs FerretDB v1 on its embedded SQLite. The same FerretDB
can store into PostgreSQL, MySQL, MariaDB or SAP HANA instead, and there was no
compose file for any of them — those backends were reachable only by editing
the default file by hand. There is one per backend now:
docker-compose-ferretdb-v1-postgresql.yml, -mysql.yml, -mariadb.yml and
-sap-hana.yml.
They differ ONLY in the database. The same WeKan image, the same ~700 lines of
environment and the same comments explaining them — they are generated from
docker-compose.yml rather than copied, and
tests/dockerComposeBackends.test.cjs compares each file’s WeKan service
against the default one line for line. A
copy drifts on the first setting somebody adds to one file, and then a user
following the PostgreSQL file gets a differently configured WeKan than the
SQLite file gives them.
Each names its own database service, runs FerretDB with the matching
--handler, and says at the top how to start, follow and stop THAT file with
docker compose -f. The images are the newest published — postgres:18,
mysql:9, mariadb:12 and SAP’s saplabs/hanaexpress — with the LTS
alternative named in a comment where there is one. PostgreSQL is confirmed
working with Meteor 3; MySQL, MariaDB and SAP HANA say in their own headers
that they are experimental, because they are. SAP HANA also needed the
wekan/FerretDB release binaries to be
built with the ferretdb_hana build tag — without it --handler=hana is an
unknown handler — so that build now passes the tag.
build.sh and build.bat offer every docker-compose*.yml in the repo now,
not four of the eight, and the test fails if a file exists that neither menu
can start, or if a menu offers a file that does not exist.
and fixes the following bugs:
Admin Panel reports show the whole instance, not the admin's own boards. Thanks to xet7.
Boards Report was empty while the Cards report beside it listed cards from
thousands of boards. It published userBoardIds(this.userId) — the boards the
ADMIN is personally a member of — and on an instance whose admin is not a board
member that is nothing at all. Until the pagination fix below, the pane hid it:
it rendered every board in minimongo, which the All Boards page had already put
there. The Files report was scoped the same way, to the cards that admin can
access.
Both cover the whole instance now, as Cards, Broken cards, Rules, Impersonation
and Recovery already did. That membership selector was also what kept those two
publications honest, so both gained an isAdmin guard at the top —
boardsReport asked only for a logged-in user before — and each count method
counts the same set its publication pages. Files and Rules name their page now
too: opening one card puts its attachments in minimongo, opening a rules editor
puts that board’s rules there, and neither belongs in a report page. The Files
page is sorted by name, because paging with no sort is paging over natural
order — where a document can appear on two pages or on none — and that sort has
an index.
The admin lists paginate honestly: one page of ten rows, index-backed. Thanks to xet7.
Admin Panel / People showed the admin on every one of its 578 pages, and Admin
Panel / Problems / Broken cards was one endless page under a pager that said
“1 / 1”. The same bug in two shapes: the pane subscribes to a publication that
sends exactly one page, then reads that page back out of minimongo — which
holds far more than the page. The logged-in user’s own record is always there,
because accounts publishes it, and so is every card of every board the admin
has opened; a plain find(query) cannot tell those apart from the page. So
the server names the page now: getPeoplePageIds returns the ids of the
People page with the publication’s own selector, sort and window, and the
Broken cards, Cards and Boards report publications send the ids of the page
they just sent alongside it. Each pane renders that list, in that order, and
nothing else.
Every paginated page in WeKan loads ten rows at a time, from one constant, including the pages that draw a pager of their own — the reports and the event streams, the four People panes, Translation, the search pages and the archive. Table.md records the rule and its two deliberate exceptions.
And the counting behind those pagers is index-backed. Every count already
asked the database for a count rather than fetching the rows, but several
counted and paged on unindexed fields: People sorts users newest-first, so on
an instance with 14000 users every page of ten sorted all 14000 first, and
Broken cards asks an $or over boardId/swimlaneId/listId/type, which
uses no index at all unless every branch has one. The missing indexes are
created at startup by the idempotent ensureIndex, so an upgrade adds them by
itself. tests/adminPageRows.test.cjs and tests/paginationIndexes.test.cjs
pin all of it.
and has the following release-tooling changes:
The release workflow says what worked, what failed, and why. Thanks to xet7.
release-all.yml could fail in ways that left no explanation, or no failure at
all: a version bump that committed nothing, a release published with empty
notes, a bundle zip that exists but holds no bundle/main.js, an upload that
reports success and leaves no asset behind, and a multi-arch image whose
manifest is missing a CPU — which nobody notices until a user on that CPU is
told there is no matching manifest. Each of those is checked now, and each
check says the same three things when it fails: what was expected, what was
actually there, and what to do about it. On success it prints one OK: ...
line naming what it verified, so the log shows what worked and not only what
did not. Every job also ends with a “Job result” step that runs whatever
happened, so the run summary is one line per job — OK, FAILED or CANCELLED —
instead of eighteen logs to open.
A missing or broken release secret is one named line in the log. Thanks to xet7.
Only three jobs said anything about their secrets. Everywhere else a missing
one surfaced deep inside a tool — a login that was refused, a checkout that
404’d — with nothing naming the secret, and the docker job did it only after
the multi-arch build had already spent half an hour on an image it could not
push. Every job that needs a secret now checks it in its FIRST step and names
what is missing. Where the answer is cheap it also checks that the secret
WORKS, which is the other half of the question: WEKAN_REPO_TOKEN is asked
whether it can push to the repository it is for, the three registry credentials
are decoded and logged in with, and the base64 ones must decode. What only the
far end can answer is answered where it is used — the Snap Store uploads name
SNAP_AUTH when they are refused, and a Launchpad build that fails with an
unauthorized in its log says LP_CREDENTIALS may be why.
Every disabled release job runs again, so its output is visible. Thanks to xet7.
snap-qemu (ppc64el, s390x), snap-launchpad (riscv64), snap-variants,
ucs and nextcloud were hard-disabled with if: ${{ false }}, so a release
never said anything about them at all. They all carry continue-on-error: true,
so running them cannot fail a release or hold up another job — the point of
running them is to SEE what they report. The three that skip their work while
their secrets are unset — snap-variants, ucs, nextcloud — do it with a
::warning:: now instead of a quiet ::notice::, so the missing secrets show
in the run summary rather than only in a log.
and has the following developer-facing change:
A test pins that a per-tenant admin sees no report data. Thanks to xet7.
Admin Panel / Problems is instance-wide, so every report publication and count
method asks for the SITE admin flag user.isAdmin — not canOpenAdminPanel,
which a per-tenant Global Admin passes — and problems is not one of a tenant
admin’s tabs. That is what keeps the Boards and Files reports honest now that
they cover the whole instance instead of the admin’s own boards, so it is
pinned rather than left to be re-derived. The one admin list a tenant admin
does get, People, is tenant-scoped in all three places that decide what it
shows: the publication, the page-ids method and the count all go through
peopleScopeSelector, so its pager cannot count rows its page may not show.
and improves the documentation:
docs/Databases is one directory per database, each with a README. Thanks to xet7.
It was a flat list in which the database was a filename prefix, in three
spellings — MongoDB-Driver-System.md, mongodb-avx-qemu.md,
MongoDB_OpLog_Enablement.md — plus FerretDB2-PostgreSQL.md and a
ToroDB-PostgreSQL directory. Somebody asking “how do I run WeKan on
PostgreSQL” had to read the prefixes to work out which files were even about
their database.
Now there is a directory each for Migrations, MongoDB, FerretDB — with
1 and 2 inside it, because v1 and v2 are different products with different
backends — and ToroDB, with PostgreSQL inside it. Each file keeps its name
minus the redundant prefix, and the old PostgreSQL.md, which was an index of
FerretDB and ToroDB rather than a document about PostgreSQL, becomes the
directory’s README.md.
Every directory has a README.md saying what is in it and where to go next —
including FerretDB/1, which had no documentation at all although it is
WeKan’s default database and had just gained four more backends. A link to a
directory points at the DIRECTORY, not at its README.md, because that file is
what is opened by default anyway. Every link into the old paths is updated: in
the docs, in docker-compose-ferretdb-v2-postgresql.yml and on the wekan.fi
website, along with the relative links inside the moved files, which gained a
directory level. tests/docsDatabases.test.cjs pins the layout, the READMEs,
the directory links, that every relative link resolves, and that nothing
outside CHANGELOG.md still names an old path.
Which other databases run on many CPUs, and what a FerretDB v1 backend would cost. Thanks to xet7.
WeKan runs on every CPU Node.js runs on, and this fork of FerretDB v1 exists
because MongoDB publishes no server for most of them. Alternatives.md in
docs/Databases/FerretDB/1
answers the two questions that keep coming back: which databases even have
images for those CPUs, and what would be missing in FerretDB v1 before it could
store into one.
The architecture table is read from each registry’s own manifest rather than from documentation, and it says plainly what that data shows: PostgreSQL is the only widely-portable database server — the only one publishing ppc64le, s390x and riscv64. MariaDB covers ppc64le and s390x, MySQL neither, and MongoDB itself and upstream FerretDB 2 are amd64 + arm64 only. This fork’s own image covers nine platforms.
What a new backend needs is taken from the code: a pure-Go driver, because the
binaries are built CGO_ENABLED=0 and that is what makes one build serve nine
architectures — which is why Oracle and IBM Db2 are out despite Db2’s ppc64le
and s390x images; the three internal/backends interfaces, none of them
stubbable; a metadata registry; the SQL features the translation actually uses,
including the record-id column that capped collections — and therefore the
OpLog — are built on; correct MongoDB semantics on top of that; and a live
integration run, which is the whole difference between the confirmed and the
experimental rows. Plus the shortcut: a database that speaks the PostgreSQL or
MySQL wire protocol needs no new backend, only an existing one that survives
its dialect — CockroachDB brings s390x that way, for the price of a test run.
A follow-up groups them, so the reader is not left to find the pattern: they are not fifteen questions but seven families — PostgreSQL-wire, MySQL-wire, enterprise SQL, embedded, columnar, key-value, and what already speaks MongoDB — and inside a family the answer is one answer. The page opens with all of it in five sentences and one table, because the conclusion (verify the three backends that already exist rather than write a fourth) was previously visible only to whoever read to the end.
Thanks to above GitHub users for their contributions and translators for their translations.