Self-hosted service
krateng/maloja avatar
krateng/maloja

Maloja refuses bare metal installs while publishing a package and a run command for them

Self-hosted music scrobble database to create personal listening statistics and charts

1,829 stars99 forksPythonGPL-3.0

At a glance

What is it?
A self-hosted scrobble database whose readme says containers only and whose packaging says otherwise: three dependency lists for one optional package, a Debian package list missing the library the Alpine one carries, and Python pinned to a single minor version.
Who is it for?
Maloja is a good fit for someone who already scrobbles and wants the statistics kept on their own machine, with a tagging schema of their choosing and no social layer, and the container image is the path its author wants you on. Two things are worth checking first.
Can I use it commercially?
Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
Is it still maintained?
Yes. The repository last received commits 54 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 4, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The readme forbids bare metal and the packaging offers it

The installation section opens with a policy rather than a command: to avoid version and dependency mismatches, Maloja should only be used in Docker or Podman and not on bare metal, and the author adds in the first person that no help can be offered for bare metal installations, while suggesting a virtual environment would help.

The packaging then does exactly what that advice rules out. The project metadata defines a distribution called `malojaserver`, declares a console script named `maloja`, and its build backend is flit, with the module name given explicitly so the distribution and the import path differ. There is a badge linking to the project page on PyPI, so the package is published and installable, and the readme itself documents a run command for the case where you are not in a container.

Three stories therefore sit side by side: a container image the author recommends, a package on a public index for anyone who prefers it, and a first person refusal to help with the third option. Nothing contradicts itself technically. It does mean the install advice and the packaging are written by someone who changed their mind about one of them, and the repository does not say which is current.

The same fourteen requirements are written in two files

The dependency list appears in the project metadata and again, line for line, in the requirements file at the root. The contents are a micro web framework, `bottle`, on a production WSGI server, `waitress`, plus two libraries that are the author's own work, `doreah` and `nimrodel`, and a set of utilities: a process title setter, a template engine, an LRU dictionary, a system information library, an ORM, a data URI parser, a file type detector using libmagic, an HTTP client, a TOML parser and a YAML parser.

The pinning style is almost uniform. Every one uses an exact major and minor with a wildcard on the patch, `sqlalchemy==2.0` excepted, which is the only line with an open range. That is a deliberate choice for a self-hosted application, where a surprise upgrade after a `pull` is the exact failure the container is supposed to prevent.

The duplication is the weaker part of the arrangement. The same fourteen lines in two files is one more place to update and one more way for the two to disagree, and since the container build has its own list as well, there are three. The repository has no tests directory in its top level entries, so nothing in the visible layout checks that the files match.

The Alpine package list carries libmagic and the Debian one does not

The project metadata also carries per-distribution package lists for building and running, and comparing the two is instructive. The Alpine list is the detailed one. Its build entries name a compiler, the Python development headers, XML and XSLT development libraries, a foreign function interface library, the C library headers, pip and the kernel headers, which is what the compiled dependencies need. Its run entries name Python, an XML library, the timezone database and libmagic.

The Debian list has one build entry, pip, one run entry, Python, and an empty optional list. So the file type detection dependency has its system library on Alpine and nothing equivalent on Debian, and the optional image library is available on one distribution and not the other.

That optional library is `pyvips`, and it is the entire content of the extra named `full`, so a distribution following the Debian list ends up without image processing while a distribution following the Alpine list gets it under `opt`. Since the readme's image feature needs those images to exist at all, this is a difference in visible behaviour rather than in build convenience.

One optional dependency is declared in three places

The `full` extra exists in the project metadata:

code
[project.optional-dependencies]
full = [
	"pyvips==2.2.*"
]

There is also a requirements file at the root whose name marks it as the extras counterpart to the main requirements file, and there are the two optional package lists just described, where `vips` appears for Alpine and nowhere for Debian. Three declarations of one optional package, in three formats, with three different scopes.

The naming around it is similarly spread out. The distribution is `malojaserver`, the module the build backend is told to use is `maloja`, the console script is `maloja`, the source directory in the repository is `maloja`, the container image is published under the author's namespace as `krateng/maloja`, the configuration file is `settings.ini`, and the documentation page is `settings.md`. None of this is ambiguous once you know it, and all of it has to be learned before you can find anything.

The other name worth knowing is the image file. The repository ships a Containerfile rather than a Dockerfile, which follows the Podman convention rather than the Docker one, and the readme points at it by that name when it suggests building the image yourself instead of pulling it.

Python is pinned to one minor version and the classifiers say three

The Python constraint in the metadata is `==3.12.*`. That is not a floor and not a range: it admits 3.12 and nothing else, so a machine on 3.11 is refused and so is a machine on 3.13 or 3.14. For a self-hosted application that the author wants run in a container, pinning the interpreter is consistent with everything else in the file, and the Alpine package lists do not pin a version either, so the image is what supplies it.

The classifier list does not match. It declares a single entry, `Programming Language :: Python :: 3`, with no minor versions underneath, and an operating system entry of OS Independent, alongside a GPLv3 licence classifier. So the metadata says any Python 3 while the constraint says exactly one of them. A tool reading classifiers would rate this package as compatible with Python 3 generally; a resolver reading the constraint would refuse most of them.

The rest of the metadata is conventional: the author is named with an email address, the keywords list covers scrobbling, music, self hosting, database, charts and statistics, and the repository, documentation and homepage URLs all point at the same repository rather than at a separate documentation site.

Two documented features need keys for other people's services

The self-hosting claim is specific: your data stays in an easily parseable form, your library is not synced with any public or official music database, and you can follow your own tagging schema. Two features then go outward on purpose.

Images need API keys. To display artist artwork you must register an application with Last.fm and with Spotify, and the readme notes both are free of charge. Maloja is not calling them for your listening data; it is calling them to obtain pictures.

Proxy scrobbling is the second. You can configure your server to forward your scrobbles to other services, which the readme frames as the reason you do not have to set up every client twice. That is the feature's entire purpose, and it means a scrobble can reach a hosted service even though your statistics stay local.

Keys live in a file called `apikeys.yml` in the configuration folder, or can be entered through the web interface, and the recommendation is to define a different key for every scrobbler you use. That is a small design decision with a large effect: it means you can revoke one client's access without revoking all of them, and it means the file is the most sensitive thing in the data directory.

Configuration has four locations and four file formats

Everything about a running instance is configurable, and none of it is configured in one place. Settings live in an ini file at `/etc/maloja/settings.ini`, documented in a settings file at the repository root, and every one of them can also be given as an environment variable with the `MALOJA_` prefix. Custom tagging rules live in a file called `rules.info` under `/etc/maloja/rules`, and a set of predefined rules can be applied from an admin page at `/admin_setup`.

Images are managed the same way, either by dragging a new picture onto the existing one in the web interface or by editing a file called `images.info` in the images folder under `/var/lib/maloja`. API keys are a third format again, a YAML file. And the container has its own three: a flag that makes first-run setup non-interactive and which the readme says the container will not work properly without, a variable that sets the admin password on the first run only, and a variable that says where in the container the configuration files live, which is the directory you mount so the data survives a pull.

Importing previous listening is simpler. You drop export files into the import folder inside your data directory, and four formats are recognised: an export from the Last.fm exporter built by a third party, the official Spotify data export, the official ListenBrainz export, and the export of another Maloja instance.

Editorial conclusion

Maloja is a good fit for someone who already scrobbles and wants the statistics kept on their own machine, with a tagging schema of their choosing and no social layer, and the container image is the path its author wants you on. Two things are worth checking first. The self-hosting claim has documented exceptions, since artist images need Last.fm and Spotify keys and the proxy scrobble feature forwards to other services on purpose. And the packaging carries three separate declarations of the same optional dependency, so a distribution that follows one file and ignores the others will not have image processing. If you want to run it outside a container, use a virtual environment and expect to read the development notes, because that route is published but explicitly unsupported.

Frequently asked questions

Can Maloja be installed without Docker or Podman?

The readme says it should only be used in Docker or Podman, not on bare metal, and that the author cannot offer help for bare metal installations. The project is also published on PyPI as malojaserver with a maloja run command, so the package exists even though that route is discouraged.

What port does Maloja listen on and how is it bound?

The container's web port defaults to 42010 and you must publish a host port to reach it. The container uses IPv4 by default, and the readme's minimum run command publishes 42010 and mounts a data directory at /mljdata.

What is MALOJA_SKIP_SETUP for?

It makes the first run setup process non-interactive. The readme states that Maloja will not work properly in a container without it set, and the supplied Containerfile sets it by default.

Which scrobble exports can Maloja import?

Four: an export generated by a third party Last.fm exporter, the official Spotify data export, the official ListenBrainz export, and the export of another Maloja instance. Files are copied into the import folder inside the data directory.

Does Maloja need API keys from other services?

Only for two features. Artist images need free API keys from Last.fm and from Spotify, and the proxy scrobble feature forwards your scrobbles to other services on purpose. API keys are stored in apikeys.yml, with a different key recommended per scrobbler.

Official sources

  1. Issues
  2. krateng/maloja on GitHub
  3. License: GPL-3.0
  4. Project website
  5. 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/krateng-maloja.svg)](https://hysenlabs.com/projects/krateng-maloja)