Self-hosted service
jonashaag/klaus avatar
jonashaag/klaus

klaus and the case for a Git web viewer that needs no configuration

docker run klaus / pip install klaus — the first Git web viewer that Just Works™.

705 stars104 forksPythonNOASSERTION

At a glance

What is it?
klaus is a Python Git web viewer that mounts your repositories and serves them with syntax highlighting, markup rendering, code navigation and push support, with no configuration file. The bundled script is a development convenience, and the project is explicit that it must not be used to serve traffic.
Who is it for?
klaus suits a developer who wants to browse a local checkout in a browser, or a team that wants a read-oriented web view of internal repositories without standing up a full forge, because the Docker image ships the highlighter, the markup renderers and ctags already configured. It does not suit anyone serving it to the internet as-is, because the bundled script is single-threaded and the project says so explicitly.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 115 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One Docker command and a directory of repositories

klaus is described as a simple, easy-to-set-up Git web viewer, and the Docker route is the one the project presents as the easiest way to get started.

The image has syntax highlighting, Markdown rendering, code navigation and the rest pre-configured, so the whole setup is one command:

bash
   docker run -v /path/to/your/repos:/repos \
              -p 7777:80 \
              -it jonashaag/klaus:latest \
              klaus --host 0.0.0.0 --port 80 /repos/repo1 /repos/repo2 ...

The structure of that command is the whole design. You mount the folder on the host that contains your Git repositories into the container at /repos, you publish port 7777 on the host mapped to port 80 in the container, and you list the repository directories as arguments to the klaus script. You then go to http://localhost:7777 on the Docker host and the repositories are there.

The documentation notes that you can pass in multiple volume arguments if your repositories live in more than one folder on the host, which is the kind of detail that decides whether a bind mount arrangement works on the first attempt.

And the project is direct about its own premise, saying that if it does not just work for you, please file a bug.

What the viewer actually gives you

The feature list is short and each entry is a real piece of work rather than a checkbox.

Syntax highlighting is there, and it comes from Pygments, which is a runtime dependency in setup.py along with the rest of the stack. Markdown and RestructuredText rendering are supported, and the Dockerfile installs the Alpine packages for Flask-Markdown and Docutils to provide them, and there is a wiki page on markup rendering for the optional extras.

Pull and push support over Git Smart HTTP is the feature that separates klaus from a read-only browser. The underlying implementation is dulwich, a pure Python Git implementation, listed as a dependency and pinned at 0.19.3 or newer. That is why the Dockerfile sets a global git safe.directory entry for every path, since the repositories are mounted from the host and git refuses to operate on directories owned by a different user.

Code navigation uses Exuberant ctags, and the Dockerfile installs the ctags package and a python-ctags3 build. That is a separate binary from git, which is why the image carries both.

The optional dependencies are the interesting design choice here. A viewer that renders Markdown and restructured text out of the box, with the renderers as separate extras, means you can get a working view of a repository without pulling in every markup parser your team might never use.

The klaus script is for testing, and the documentation says so twice

This is the most important caveat in the project and it is stated in bold in the documentation.

The klaus script is intended for testing and low-traffic local installations only. It uses wsgiref internally, which does not scale at all, and in fact it is single-threaded and non-asynchronous.

That is worth taking literally rather than as boilerplate. A single-threaded, non-asynchronous WSGI server serves one request at a time, so a browser that opens several connections at once will queue them, and a page that fans out into a dozen requests will feel slow. For a developer browsing one repository on a laptop that is invisible. For anything else it is the wrong shape.

The script itself takes a list of repositories:

bash
   # With Docker:
   docker run ... jonashaag/klaus:latest klaus [repo1 [repo2 ...]]
   # Local setup:
   klaus [repo1 [repo2 ...]]

And the same script prints its options:

bash
    # With Docker:
   docker run ... jonashaag/klaus:latest klaus --help
   # Local setup:
   klaus --help

The local setup route is a single pip install, and the project names the package on PyPI:

bash
   pip install klaus

For real traffic you use make_app and a real server

The klaus module contains a make_app function which returns a WSGI application, and the documentation gives two working examples of driving it under a production server. Configuration is read from environment variables, and a helper script is provided with the package at klaus/contrib/wsgi.py.

The uWSGI example:

bash
   uwsgi -w klaus.contrib.wsgi \
         --env KLAUS_SITE_NAME="Klaus Demo" \
         --env KLAUS_REPOS="/path/to/repo1 /path/to/repo2 ..." \
         ...

The Gunicorn example:

bash
   gunicorn --env KLAUS_SITE_NAME="Klaus Demo" \
            --env KLAUS_REPOS="/path/to/repo1 /path/to/repo2 ..." \
            klaus.contrib.wsgi

The two environment variables are the entire configuration surface, KLAUS_SITE_NAME for the name shown in the interface and KLAUS_REPOS as a space separated list of repository paths. That matches the zero-configuration claim: there is no config file, no database, and no settings module, just two variables.

The Docker image has uwsgi preinstalled, so the container can be run the same way:

bash
   docker run ... jonashaag/klaus:latest uwsgi ...

The wiki has a deployment section, and the project points at it from here.

The dependency set explains what kind of tool this is

The setup.py in the repository lists the runtime dependencies, and the list is short enough to read as a description of the project.

Flask and Werkzeug at 0.15.0 or newer provide the web layer, Pygments provides syntax highlighting, httpauth at 0.4 or newer handles authentication, humanize formats values, and dulwich at 0.19.3 or newer provides the Git implementation. That is the entire stack, and notably there is no database library, no JavaScript build chain, and no ORM.

The package is version 3.0.1, with the description The first Git web viewer that Just Works, and it is classified as Development Status 5, Production/Stable. It supports Python 2.7 and Python 3 in the classifiers while requiring 3.7 or newer in python_requires, and the console entry point is registered as klaus=klaus.cli:main.

The image build follows the same minimalism. It starts from alpine:edge, adds the Alpine edge testing repository, and installs uwsgi-python3, git, ctags and the Python packages for markupsafe, pygments, dulwich, humanize, flask, flask-markdown and docutils in a single layer. Python build tooling is added temporarily, python-ctags3 is compiled and installed, and then the build packages are removed in the same layer so they do not ship in the final image. The source is copied in, pip-installed, and the source directory deleted, which is the standard way to get a clean image without leaving the build context inside it.

Where klaus is the wrong tool

The first case is the one the documentation itself flags. If you are going to serve this to other people, the bundled script is not the answer, and moving to a real WSGI server means you own the process management, the worker count and the reverse proxy in front of it. klaus provides the application, not the operations.

The second case is collaboration. klaus has push support over Git Smart HTTP, but it is a browser for repositories, not a forge. There is no pull request workflow, no issue tracker, no review process, no permissions model beyond what the httpauth dependency provides, and no notion of a repository you should not be shown. Anyone who needs those things wants a forge, and a web viewer is not a smaller forge.

The third is scale of history. A viewer that renders a repository tree and file contents is comfortable with a project of a few thousand commits. Point it at a repository with a decade of history and a very large tree, and the cost of walking that tree on every page load is a property of the design rather than a configuration choice.

And there is the licence question, which the project leaves slightly inconsistent. The documentation states ISC, described as BSD, and the package classifier in setup.py says ISC License (ISCL), while the recorded licence identifier for the repository is a custom one that GitHub cannot categorise. If you are putting this in a company, read the LICENSE file in the repository root rather than relying on either of the other two statements.

Editorial conclusion

klaus suits a developer who wants to browse a local checkout in a browser, or a team that wants a read-oriented web view of internal repositories without standing up a full forge, because the Docker image ships the highlighter, the markup renderers and ctags already configured. It does not suit anyone serving it to the internet as-is, because the bundled script is single-threaded and the project says so explicitly. Before you deploy it, use a real WSGI server with the klaus.contrib.wsgi module and set KLAUS_SITE_NAME and KLAUS_REPOS as environment variables, and note that the licence statement in the documentation and the one recorded for the repository do not match, so read the LICENSE file before you rely on it.

Frequently asked questions

What is klaus?

It is a simple, easy-to-set-up Git web viewer written in Python. It provides syntax highlighting, Markdown and RestructuredText rendering, pull and push support over Git Smart HTTP, and code navigation using Exuberant ctags, and it is designed to need no configuration.

How do I run klaus with Docker?

Mount the folder containing your repositories into the container, publish a host port to container port 80, and list the repository directories as arguments to the klaus script, for example docker run with a volume, a port mapping and klaus --host 0.0.0.0 --port 80. Then open the published port on the host.

Can I use the klaus script to serve real traffic?

No. The documentation marks the klaus script as intended for testing and low-traffic local installations only, because it uses wsgiref internally, which is single-threaded and non-asynchronous. For real traffic, use the make_app function with a real server such as uWSGI or Gunicorn against klaus.contrib.wsgi.

How is klaus configured for a production server?

Through environment variables only. Set KLAUS_SITE_NAME to the name shown in the interface and KLAUS_REPOS to a space separated list of repository paths, then point uWSGI or Gunicorn at the klaus.contrib.wsgi module that ships with the package.

What licence is klaus released under?

The documentation states ISC, described as BSD, and the setup.py classifier says ISC License (ISCL), but the recorded licence identifier for the repository is a custom one that GitHub cannot categorise. Read the LICENSE file in the repository root to settle the question before use in a commercial setting.

Official sources

  1. Issues
  2. jonashaag/klaus on GitHub
  3. README
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/jonashaag-klaus.svg)](https://hysenlabs.com/projects/jonashaag-klaus)

Was this useful?