Feather is a Swift CMS whose Makefile is the real documentation
Feather is a modern Swift-based Content Management System.
At a glance
- What is it?
- Feather is a Swift content management system built as a set of services around Postgres, with a migrator, a backend server, a worker, a web app, a static server and OpenAPI apps, all driven by Make targets. The readme opens with a work in progress warning, and the project publishes no releases.
- Who is it for?
- Feather suits a developer who wants to run a Swift content system as a set of separate services with a real migration step, a background worker and a generated API contract, since the make targets give you a coherent local workflow and the module layout keeps each feature's API definition next to its code.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Swift, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The warning at the top is the most important line
Feather is described as a modern Swift based content management system, and the readme opens with a banner of warning signs and the words work in progress. That is the first thing a reader sees, and it should be read as an operating instruction rather than as modesty. A content management system that is under active development will change its data model, its API and its configuration, and anyone adopting it needs a migration story, which the project does provide, but the readme does not claim stability anywhere. The rest of the document is written with the confidence of a project that has its architecture settled and its interfaces still moving. The homepage is feathercms.com, the topics list includes api, async-await, blogging, engine, framework, mustache, publishing, swift and swift-6, and the repository publishes no GitHub releases, so there is no version to pin and no changelog of tagged artifacts to read. The last push was on 2026-10-22, so the work is current.
Make targets are the interface to the whole system
The readme does not describe the architecture in prose, it lists the make targets, and the list is the architecture. There is a clean target that clears only the Postgres database volume and the media storage volume, and a reset target that drops the full compose stack including all compose managed volumes, so the two are deliberately different operations. There is a stop target that stops the services without removing them, and a deps target that builds and runs the dependency services, which the readme names as the certificates service, Postgres and the migrator. The application target builds the shared application artifact image once, builds the runtime images from it, and runs the application stack, described as the dependencies, the migrations, the backend workers, the web app, the static server and the OpenAPI services. Then there is a family of local targets, one that runs Postgres in Docker with the rest as local Swift processes, one for the web app alone, and one for the backend without the front end. The distinction between application and local is the useful one for a developer, because the local mode puts the Swift code you are editing in front of you while the application mode runs the whole thing in containers.
A dependency stack with certificates, Postgres and a migrator
The compose file shows the dependency services, and the design is more careful than most. A certificates service builds and populates a volume with certificates, and it runs to completion rather than staying up. Postgres depends on it completing successfully, and the server waits for the certificates to finish, for Postgres to be healthy and for the migrator to complete successfully, so the start order is enforced by the compose file rather than by retry logic in the application. The database itself is exposed on the standard port with default credentials in the example configuration, and it carries a healthcheck that is worth reading closely, because it does not just ping the server. It runs pg_isready with a host, an SSL mode that verifies the certificate authority, a root certificate path pointing at the generated file, and the user, password and database name, and it polls every second with a five second timeout and ten retries. So the container is considered healthy only when a TLS client can actually connect and verify against the certificate the certificates service produced.
Public origins are separate from internal addresses
The configuration separates what the browser sees from what the services see, which is the kind of detail that saves an afternoon when a deployment misbehaves. Three variables control the browser facing URLs, the public base URL for the web and admin app, the public base URL for static assets, and the public base URL for backend served media assets, with compose defaults of localhost on ports 3456, 4567 and 8080 respectively. These are explicitly separate from the internal API base URL that the web app uses to talk to the backend service, and the readme states that plainly. The make targets handle the local case by detecting the current LAN address, falling back to the localhost defaults when the detection fails, and the readme shows how to override any of the three by setting the variables on the command line before make all. The reason the distinction exists is that in a containerised deployment the internal service name and the address a browser can reach are different things, and mixing them is a common source of broken asset URLs.
The worker has a concurrency trap worth understanding
The worker section describes a scheduling behaviour that is easy to misread. General job concurrency is kept at a value of sixteen workers by default, while ImageMagick and FFmpeg asset processing is limited separately to one at a time by default, and duplicate media jobs for the same asset are ignored while that asset is already being processed. The last sentence is the interesting one: media jobs waiting for a processing slot still occupy worker handlers, so a large media backlog can temporarily reduce capacity for other job types. In other words, the dedicated media limit does not protect the rest of the queue the way a separate pool would. If you import a large library of images and videos through the CMS, unrelated background work can stall behind them, and the fix is to understand the design rather than to raise the media limit. The worker also sends email through Amazon SES, with credentials kept in a local env file next to the compose file, which Git ignores.
SES_ID=your-aws-access-key-id
SES_SECRET=your-aws-secret-access-key
SES_REGION=eu-central-1Modules own their contracts, and the tree shows a monorepo
The top level layout describes a monorepo with a clear split. There is a core directory, an application directory for the executables, a modules directory holding the feature packages, a docker directory with the per service build files, a scripts directory, a documentation directory and a compose file at the root, plus an agent instructions file, a todo file and a licence. The OpenAPI story is the part that shows the module boundary is real rather than cosmetic. The generators live in the owning module packages under the modules directory, and the make yaml target regenerates the module contracts, which means each module owns its own API definition instead of a central schema file being edited by hand. The compose file confirms the service granularity, with separate server, worker, web, web-static, openapi-app and openapi-admin services, each with its own image and its own dependency conditions, so the admin and public API surfaces are deployed as separate applications rather than routes in one binary.
Editor integration is documented down to the keybinding
The readme spends an unusual amount of space on VS Code, and that is a sign of how the project expects to be worked on. There is a note that opening the workspace folder for the first time is slow because the Swift package search in subfolders setting scans the tree, and a set of keybindings to add to the keyboard shortcuts file.
[
{
"key": "cmd+ctrl+r",
"command": "workbench.action.debug.selectandstart"
},
{
"key": "cmd+ctrl+i",
"command": "workbench.action.tasks.runTask",
"args": "Ensure Local Dependencies"
},The workflow those bindings encode is a four step loop: run the ensure local dependencies task once per session, start the debug session, open the app in a browser, and stop the runtime and free the ports when you finish. The instruction to run the dependency task only once per session, to avoid errors, is the kind of operational detail that only appears in a project where somebody has already hit the problem. There is also a documented Docker BuildKit cache error, with the fix being to enable the containerd image store in Docker Desktop settings, and the test workflow, which starts Postgres and the certificate services, runs the backend and module tests either from the test explorer or through make test and make test-all, and tears the services down afterwards.
Editorial conclusion
Feather suits a developer who wants to run a Swift content system as a set of separate services with a real migration step, a background worker and a generated API contract, since the make targets give you a coherent local workflow and the module layout keeps each feature's API definition next to its code. It is the wrong choice for a site that needs to be live this afternoon, because the readme marks the project as work in progress with breaking changes expected, and there are no tagged releases to pin. Verify three things before you build on it: that the module and OpenAPI generation workflow fits your team, since each module owns its own contract and the make yaml target regenerates them, that you can live with Postgres being the only datastore and the media volume being a Docker volume, and that your deployment can run the migration container as a separate gated step, because several services wait for it to complete before they start. The licence is MIT and the last push was on 2026-10-22.
Frequently asked questions
How do I run Feather CMS locally?
The make targets do it. The local target runs Postgres in Docker and the migrator, backend server, worker, web app and static server as local Swift processes, with media stored in the local docker media directory. The application target instead runs the whole stack in containers, and make all builds and runs the full local stack.
Is Feather CMS ready for production use?
The readme opens with a work in progress banner, and the project publishes no GitHub releases. Treat the data model and API as moving, plan migrations accordingly, and pin your own version rather than tracking a tag.
How are the API contracts produced in Feather?
The OpenAPI generators live in the owning module packages under the modules directory, and the make yaml target regenerates the module contracts. The compose stack then runs separate services for the public API and the admin API.
What are the public origin variables used for?
They set the browser facing URLs for the web and admin app, static assets and backend served media, defaulting to localhost on ports 3456, 4567 and 8080. They are separate from the internal API base URL used between services, and the make targets detect the LAN address or let you override them on the command line.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/feathercms-feather)Was this useful?