Self-hosted service
zotonic/zotonic avatar
zotonic/zotonic

Zotonic: an Erlang web framework that ships with a CMS attached

Zotonic - The Erlang Web Framework & CMS

850 stars209 forksErlangApache-2.0

At a glance

What is it?
Zotonic is an Apache-2.0 Erlang web framework and content management system that handles content editing, and it can be run from a download, a Docker image or a Podman image with PostgreSQL behind it. It suits teams that want a real time Erlang stack with an editor interface already built, and it does not suit a team that wants a content system with a large third party ecosystem.
Who is it for?
Zotonic suits a team that has chosen Erlang for a real time web service and wants content editing, an administration interface and a rendering pipeline already in place rather than assembling them, and the fact that Dialyzer and Test are separate CI jobs shows the project takes type level checking seriously.
Can I use it commercially?
Yes. Apache-2.0 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 Erlang, 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

A framework where the CMS is not an afterthought

Zotonic is described as the open source, high speed, real-time web framework and content management system, built with Erlang.

The pairing is the point. Most content systems are an application built on a framework, so the framework and the CMS are separate things with separate release cycles, and a project that wants a different rendering pipeline ends up fighting the CMS. Zotonic presents itself as one product, designed from the ground up to support dynamic, interactive websites and mobile solutions, with an elegant backend for managing content.

That is the appeal for a team that has committed to Erlang for the real time part of their stack and does not want to bolt a CMS on the side. Erlang's concurrency model and Zotonic's real time claims fit together, and the repository layout backs the single product framing, with an apps directory holding the application modules, apps_user for the modules you add yourself, and a src directory for the framework itself.

One thing to be careful about is the performance language. The README calls Zotonic fast and stable, and describes it as suited to anything from basic websites to complex distributed applications, but it does not cite a benchmark, a throughput figure or a comparison against another framework. Treat those as a direction of travel rather than a measurement, and do your own testing if throughput is the deciding factor.

The audience is therefore Erlang developers and teams building interactive sites who want content management included.

Postgres sits behind the development container

The repository ships a compose file, and the comments in it explain the arrangement before the configuration does. Start it with ./start-docker.sh, and it starts two containers: postgres, storing its data on the volume pgdata, and zotonic, with Erlang and other tools, which mounts the Zotonic home directory and builds Zotonic in the ./_build directory.

The configuration is short enough to read in full.

yaml
    postgres:
        image: docker.io/library/postgres:16.2-alpine
        hostname: postgres
        restart: always
        environment:
            POSTGRES_USER: zotonic
            POSTGRES_DB: zotonic
            POSTGRES_PASSWORD: zotonic
        volumes:
            - pgdata:/var/lib/postgresql/data
        ports:
            - '${DB_FORWARD_PORT:-5432}:5432'

The application container is built from docker/Dockerfile.dev with the working directory mounted at /opt/zotonic, and it carries three environment variables: ZOTONIC_PORT, ZOTONIC_SSL_PORT and ZOTONIC_APPS, the last pointing at the apps_user directory where your own modules live. The service publishes 8000 and 8443 on the host.

The database port is parameterised through the .env.example file, which contains a single line, DB_FORWARD_PORT=5432, with a comment explaining that it controls the port the postgres container is exposed on. That is the one knob to change if you already run PostgreSQL locally.

There is a Podman equivalent, with start-podman.sh in the root, and a plain start.sh for starting Zotonic inside an already running container.

Dialyzer as a separate CI job is the signal about code quality

The badge row at the top of the README is short, and one entry carries more information than the others.

There is a Test badge, a Dialyzer badge, a GitHub release badge, a Crowdin badge for translations, and a Docs badge that is present but commented out in the markup.

Dialyzer is the interesting one. Dialyzer is Erlang's static type analysis tool, and running it as its own CI job rather than folding it into the test run means type errors fail the build separately from test failures. For a framework that will sit under other people's applications, that is a meaningful signal about how the project treats its own contracts, and it is a signal you can check in the actions list yourself rather than take on trust.

The rest of the repository is consistent with a serious approach to tooling. There is an elvis.config, which configures the Elvis linter for Erlang code, and an erlang_ls.config for the Erlang language server. There is a .editorconfig, so the formatting rules are shared across contributors and editors, and a rebar.config with a rebar.config.script and a committed rebar.lock, so dependency versions are pinned.

The build itself is driven by a GNUmakefile with a Makefile that delegates to it, and the root contains bin, priv, release, doc, nix, docker, cloud-init and hooks directories. The nix and cloud-init directories are the ones worth noticing, because they tell you the project is expected to be deployed in a reproducible way and to boot in a cloud environment.

Translations run through Crowdin, and the credits are in the repository

Zotonic ships a Crowdin badge in the README, and the repository contains a crowdin.yml at the root, along with TRANSLATORS and CONTRIBUTORS files and an AUTHORS file.

That combination is a small but concrete picture of the project's shape. Crowdin is a translation platform, and a crowdin.yml is the configuration that tells it where the source strings live and where the translated ones should be written back. For a content management system, having the user interface strings in a translation workflow is not a cosmetic decision, since the editing interface is part of the product that non English speaking editors use.

The presence of a translators file alongside a contributors file and a code of conduct free AUTHORS file is the other side of it, a project that keeps its human credits in version control rather than only in commit metadata.

The README also thanks the services that support the open source work, and those are listed as browserstack for browser testing and crowdin for translation. The browser testing detail matters for a framework whose selling point includes dynamic interactive pages, since it implies the test suite runs against real browsers.

For anyone evaluating the project, this tells you the internationalisation is a live process with external tooling, not a folder of hand translated files that will drift.

Version 0.94 with releases every few weeks

The version history is a useful signal about the maintenance pace. The most recent releases are 0.94.1 on 2026-09-01, 0.94.0 on 2026-08-25, and 0.93.0 on 2026-07-07.

So this is a 0.x line, which in semantic versioning terms means the public interface is not yet frozen, and the minor component is where new things arrive. It also means a patch release such as 0.94.1 can carry changes that would be a major bump in a 1.x project, and the release notes are the place to check what actually moved.

The cadence is roughly monthly across three releases, with a gap of about two months between 0.93.0 and 0.94.0. Combined with a last push to the repository on 2026-10-19, that is a project that is being worked on continuously rather than one that only wakes up for annual maintenance.

The release notes are published on the project website, at zotonic.com/docs/1643/release-notes, and the README links them directly. For a framework where the version number carries less certainty than it would at 1.0, the release notes are the artifact to read before each upgrade, and the version number itself is kept in a VERSION file in the repository root.

The licence is Apache 2.0, with the LICENSE file in the repository, which is the permissive grant most teams expect and is worth contrasting with copyleft frameworks if you intend to extend Zotonic itself.

Three supported ways to start it

The installation section of the README is deliberately short, and it points at three routes.

The first is to download Zotonic from the official website, with the download page linked at zotonic.com/download. The second is to start Zotonic from a Docker or Podman image, with a documentation page at zotonic.com/docs/1411/docker. The third is simply to read the installation chapter in the documentation, at zotonic.com/docs/1526/getting-started, under the installation section.

Notice what is not in the list. There is no build from source step in the README, no package manager command, and no list of operating system requirements. For a project with a download, a container image and a documentation chapter, that is a reasonable choice, and it keeps the README from becoming a second copy of the manual that would drift.

It does mean you have to go to the website for the details, and the two things worth checking there before you start are the current system requirements and the release that the download page points at, since the download and the container image may not be at the same version at any given moment.

The documentation set is broader than the README as well, with a developer guide at zotonic.com/developer-guide, the release notes, a contributing chapter at zotonic.com/docs/1642, and an installation chapter, and all of it is on the project website rather than in the repository. The repository itself carries a doc directory as well, so both exist.

Security reports have their own address

Contributing and reporting problems are described separately, and the split is worth noticing.

For general issues, the README points at the issue tracker on GitHub. For security related issues, it gives a specific address, [email protected], rather than asking people to open a public ticket.

That is the correct arrangement, and for a framework it is more than a formality. A content management system runs with file upload, template rendering and a database, so a vulnerability report is about the trust boundary of the whole stack rather than about one application. Reporting privately gives the maintainers the option to publish a fix at the same time as the advisory, which is what a site operator needs in order to decide when to upgrade.

For contributors, the README asks you to read the contributing chapter in the documentation first, and describes Zotonic as an open source project made possible by the community.

The general shape of the project is therefore a well organised open source project with a published documentation site, CI that includes type checking, a translation platform, a security contact, and an Apache 2.0 licence. What it does not have, at least in the README, is a plugin catalogue, a marketplace or a list of commercial extensions, so anyone expecting the ecosystem breadth of the larger content systems should factor that in before committing.

Editorial conclusion

Zotonic suits a team that has chosen Erlang for a real time web service and wants content editing, an administration interface and a rendering pipeline already in place rather than assembling them, and the fact that Dialyzer and Test are separate CI jobs shows the project takes type level checking seriously. It does not suit a team that wants a large catalogue of third party plugins, nor one that is unwilling to run PostgreSQL, since the development container is built around a postgres:16.2-alpine service with the application user, database and password all set to zotonic. Before you commit, read the installation chapter at zotonic.com/docs/1526, start the development stack with ./start-docker.sh and confirm the ports 8000 and 8443 are free on your machine, and check the release notes for the 0.94 line, because the last push to the repository was on 2026-10-19 and the newest tag is 0.94.1 from 2026-09-01.

Frequently asked questions

What is Zotonic built on?

Zotonic is an Erlang web framework and content management system, released under the Apache 2.0 License. The repository root lists an apps directory for the application modules, apps_user for your own modules, and a src directory for the framework itself.

How do I run Zotonic with Docker?

Start it with ./start-docker.sh, which brings up two containers: postgres:16.2-alpine and a zotonic container built from docker/Dockerfile.dev. The zotonic service publishes ports 8000 and 8443, and the database is exposed on the port set by DB_FORWARD_PORT in the .env.example file, which defaults to 5432.

Does Zotonic support running under Podman?

Yes. The repository root contains start-podman.sh alongside start-docker.sh, and the README lists starting Zotonic from a Docker or Podman image as one of the supported installation routes.

How does Zotonic handle translations?

The project uses Crowdin for translations, with a crowdin.yml file in the repository root and a Crowdin badge in the README. The repository also keeps a TRANSLATORS file and a CONTRIBUTORS file.

How do I report a security problem in Zotonic?

The README asks that security related issues be sent to [email protected] rather than to the public issue tracker, while general issues go to the GitHub issue tracker.

Official sources

  1. License: Apache-2.0
  2. Project website
  3. README
  4. Releases
  5. zotonic/zotonic on GitHub
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/zotonic-zotonic.svg)](https://hysenlabs.com/projects/zotonic-zotonic)

Was this useful?