DEV Community

Zoricic
Zoricic

Posted on Edited on

Docs as Code with Confluence

Documentation drifts the moment it leaves the repository.

The code gets a pull request, a review and a test run. The Confluence page that describes it gets an edit when someone remembers, which is usually after a support engineer has answered a ticket from a page that is a release behind.

Docs as code is the usual answer: keep the docs as Markdown next to the code, so every change rides in the same pull request as the code it describes. It is a good answer — with one missing step. The people who need the docs don't read them in Git. They read them in Confluence, and Confluence does not read Git.

This article adds that missing step: the repository stays the source of truth, and Confluence becomes a mirror of the docs on main that nobody edits by hand.

The Problem

Two systems hold the same knowledge and only one of them is reviewed.

  • The repository has the code, the tests and, if the team is disciplined, a docs/ folder. Every change is diffed, reviewed and merged.
  • Confluence has the page people actually open: support engineers, product managers, the new hire, the auditor. It has search, comments, PDF export and permissions. It has no idea the handler changed on Tuesday.

The usual workarounds all fall short. Copy-paste on release day is a chore that stops after the second release. A Markdown macro that embeds a file from GitHub needs an OAuth grant to the Git host — which most security teams refuse for private repositories. Even where it is allowed, the embedded text is invisible to Confluence search and broken in PDF export. A link from the page to the repo sends readers to a place they cannot read.

The missing step is not writing better docs. It is moving the finished docs to where readers are, automatically, after every merge.

Docs as Code in One Paragraph

Keep the documentation in the repository as plain Markdown: one file per page, folders for structure, Mermaid or PlantUML blocks for diagrams. Docs change in the same pull request as the behaviour they describe, and main is the only version that counts. Everything downstream — a static site, a PDF, a Confluence space — is a build artifact regenerated from main, never edited directly. If a page is wrong, the fix is a commit, not a page edit.

Publish on Merge

Publishing should be the simplest, most mechanical step in the pipeline. When the pull request merges to main, a CI step sends the changed Markdown files to Confluence and the pages update. No judgement, no model, no human.

RepoPages is a Marketplace app built for exactly this step. The CI runner reads the files the merge changed, renders the Mermaid and PlantUML blocks to SVG, signs the payload with a secret created in Confluence, and posts it. The whole setup is one workflow file and two secrets:

# .github/workflows/repopages.yml
name: RepoPages
on:
  push:
    branches: [main]
    paths: ['docs/**/*.md']
jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 2 }
      - uses: repopages-app/ci@v1
        with:
          url: ${{ secrets.REPOPAGES_URL }}
          secret: ${{ secrets.REPOPAGES_SECRET }}
          prefix: docs/
Enter fullscreen mode Exit fullscreen mode

Real pages, not embeds

The app writes one real Confluence page per Markdown file. That distinction carries most of the value:

  • Search works. The page body is native Confluence content, so site search finds it like any hand-written page.
  • Everything around the page works too: comments, mentions, permissions, page history, mobile, PDF and Word export.
  • Structure follows the repo. Folders become parent pages, relative links between files become page links, a deleted file archives its page, a renamed file moves it.
  • Diagrams are first-class. Mermaid and PlantUML fences arrive as rendered SVG. The diagram source stays on the page in a collapsed block so it remains searchable, and is dropped from PDF and Word exports so they stay clean.
  • Provenance is visible. Each page carries a "Synced from repo@sha" byline that links to the commit, so anyone tempted to edit the page sees where the source lives.

Built for the security review

Every Confluence app gets a security review, and this architecture is shaped by it:

  • The app never connects to the Git host. The only thing that reads the repository is the team's own CI runner. There is no OAuth grant, no GitHub app, no deploy key handed to a third party. Private, self-hosted and VPN-only repositories work unchanged.
  • Nothing leaves Atlassian. The app makes no outbound calls and stores no copy of the content outside the Confluence site.
  • The app holds no token. The push is signed with a secret created in Confluence; the app verifies the signature and writes the pages. There is no credential to leak in either direction.

What Changes for the Team

  • One review instead of two. Code and docs are approved together. The Confluence page is never "a bit behind".
  • Readers keep Confluence. Nobody has to be sent to a Git host they cannot read, and nobody loses search, comments or export.
  • The repository is the single source of truth. Confluence becomes read-only by convention, and the byline makes the convention self-explaining.
  • Publishing stops being a task. There is no release-day checklist item, because the mirror updates on every merge.

Limitations

  • RepoPages keeps Confluence matching main. It cannot know whether main's docs match the code — that stays a review habit. A cheap way to back it up: a short AGENTS.md that maps code areas to doc pages, so a coding agent updates the mapped pages in the same pull request. The demo repository below shows that setup.
  • Diagrams are rendered on the CI runner, so the pipeline needs mermaid-cli or PlantUML available. The ready-made action and Docker image include them; a bare runner does not.
  • Confluence edits don't flow back. The mirror is one-way by design. A team that wants to co-author pages in Confluence should keep those pages out of the synced space.
  • Read-only is a convention, not a lock. Someone can still edit a synced page; the next merge that touches the file overwrites the edit. The byline is there to make that outcome unsurprising.

The Bottom Line

Docs as code puts the documentation where it gets written, reviewed and versioned. The missing step was moving the result to where people read it. A signed push on merge adds that step — the page people read becomes the page the team reviewed, automatically, on every merge to main.

Resources

Top comments (0)