The Self-Hosted Dev Stack: Forgejo, Redmine, and Docmost
The default move for developer tooling in 2026 is SaaS. GitHub for code, Linear or Jira for tickets, Notion for docs. The integrated experience is real, reliability is generally fine, and the cost sneaks up on you once you’re paying for seat licenses, API tiers, and storage charges.
But cost isn’t why I moved this in-house. The data posture is.
Code repositories have business logic, security research, personal projects — things you’d rather not see crawled for a training dataset. Documentation has architecture decisions, threat models, operational notes. None of that should live exclusively in someone else’s cloud by default.
This isn’t about not trusting GitHub or Notion specifically. It’s about not having a clean answer when someone asks where that data actually lives and who can see it.
Forgejo: git without the platform baggage
Forgejo is a community fork of Gitea. Full git hosting — web interface, SSH cloning, pull requests, CI hooks, package registry — running on a single modest server.
I picked Forgejo over Gitea for governance reasons. Forgejo is explicitly community-run with a published roadmap. Self-hosted infrastructure should be one acquisition away from nothing, not one acquisition away from a pricing surprise.
In the Mosburn Lab, Forgejo runs on Docker with a PostgreSQL backend. The mosburn.forgejo role handles container deployment and lifecycle, database provisioning, app.ini configuration via Ansible template, and Keycloak OAuth2 provider registration via the Forgejo admin API.
That last piece is worth calling out. After the service starts, the role POSTs to /api/v1/admin/oauth2 to register Keycloak as an OIDC provider. The call is idempotent — a 422 response means the provider already exists, which the role treats as success. First run or fiftieth, the end state is the same.
Redmine: old, stable, zero dollars
Redmine has been around since 2006. Parts of the UI show it. The plugin ecosystem is extremely stable, the API is well-documented, and it’s been free for almost two decades without that changing.
I’ve used Jira, Linear, Plane, Basecamp. For a home lab and personal projects, the overhead of any of those — including self-hosted options — is more than I need. Issue tracker, time logging, wiki. That’s what I use. Redmine has all of it.
The mosburn.redmine role is the most complex in the stack. The core Docker image gets extended with a custom Dockerfile that installs the redmine_openid_connect plugin at build time:
FROM redmine:6
RUN apt-get update -qq \
&& apt-get install -y --no-install-recommends git \
&& git clone --depth 1 \
https://github.com/CACI-IMG/redmine_openid_connect.git \
/usr/src/redmine/plugins/redmine_openid_connect \
&& bundle install --without development test
OIDC configuration lives in a configuration.yml template Ansible renders with the Keycloak issuer URL, realm, and client credentials. Redmine picks it up at startup. Users get a “Sign in with Keycloak” button alongside the local auth form.
Docmost: Notion, but yours
Docmost is newer — collaborative docs platform, block-based editor, nested pages, real-time collaboration. Think Notion without the pricing page.
It’s the simplest of the three to deploy. The entire configuration is environment variables:
OIDC_ENABLED: "true"
OIDC_PROVIDER_NAME: "Mosburn"
OIDC_CLIENT_ID: "docmost"
OIDC_CLIENT_SECRET: ""
OIDC_ISSUER: "https:///realms/"
Ansible renders these into the Docker Compose environment block. No config files to manage, no plugin installation, no custom image.
The pattern underneath all three
PostgreSQL for persistent storage. Keycloak for authentication. Ansible for deployment, configuration, and lifecycle. systemd managing the Docker Compose layer on the host.
The native installation path — mosburn_*_use_docker=false — follows the same pattern with the packaging system substituting for Docker. Fedora and Ubuntu task files handle the platform differences; the Ansible interface stays the same.
Updating Forgejo means changing mosburn_forgejo_version in defaults/main.yml and rerunning the playbook. Rotating a database password means updating the value and rerunning. The playbook is the single source of truth for what’s running and how it’s configured.
Not just services that work. Services whose state is fully described in version-controlled code and can be reproduced exactly. That’s the goal.