Open-source project
Paymenter/Paymenter avatar
Paymenter/Paymenter

Paymenter: a self-hosted billing panel for hosting resellers

Free and open-source webshop solution for hostings

2,783 stars559 forksPHPMIT

At a glance

What is it?
Paymenter is a PHP billing panel aimed at hosting providers, built as a Laravel application with a three stage container image. Its environment file asks for more than its requirements page does, and its startup quietly restores default themes and extensions over your own.
Who is it for?
Paymenter suits a hosting reseller who wants recurring billing in one self-hosted panel and can run PHP 8.3 on MariaDB with Redis behind the cache and queue. It does not suit a general merchandise store, and it documents no upgrade or rollback path.
Can I use it commercially?
Yes. MIT 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 PHP, 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

Who this panel is actually built for

Two one-line descriptions sit in the same repository and they do not agree. The project summary calls Paymenter a free and open-source webshop solution for hostings, while the body copy calls it an open-source billing platform tailored for hosting companies. A webshop sells products through a cart, and a billing panel charges for things that renew without anyone sending an invoice by hand, which is the second job Paymenter is built for: a company renting server space or web hosting to its own customers.

Underneath sits PHP on a Laravel skeleton with MariaDB for data, and the topic list adds ecommerce, hosting, marketplace, stripe and pterodactyl to php and laravel. Files at the repository root give the shape of it: app/, config/, database/, extensions/, lang/, routes/, storage/, themes/ and tests/, plus artisan, composer.json, vite.js, update.sh, phpstan.neon, pint.json, phpunit.xml and crowdin.yml for translations. A sponsor banner and a Discord invite sit above that list, and readers are pointed at a live demo at https://demo.paymenter.org and an extension catalogue at https://paymenter.org/marketplace.

Three build stages pin PHP 8.3 and Node 22

Paymenter ships one Dockerfile that does the work of three images. The stage named final starts from php:8.3-fpm-alpine, installs nginx, dcron, supervisor and curl through apk, then compiles bcmath, gd, pdo_mysql, zip, intl, sockets and gmp with docker-php-ext-install before adding the phpredis extension with pecl install redis. Build tools are installed and deleted again inside that layer, so autoconf, make, g++, gcc, libc-dev and linux-headers never reach the final image.

dockerfile
FROM --platform=$TARGETOS/$TARGETARCH php:8.3-fpm-alpine AS final
FROM --platform=$TARGETOS/$TARGETARCH node:22-alpine AS build
FROM final AS production

Line one is the PHP side, line two the JavaScript side, and line three moves the compiled assets back into the first stage. Only /app/public crosses that boundary, which is why a customised theme cannot be baked into the image and has to be mounted or copied at start time instead.

The JavaScript stage is not generic asset work. package.json lists vite and laravel-vite-plugin for the storefront along with @tailwindcss/vite and tailwindcss, while the admin scripts call npx tailwindcss straight against resources/css/filament/admin/theme.css. That path is the clearest evidence that the administrative side is Filament, and it is also why a Node toolchain has to exist at build time even though the published requirements never name one.

Where the requirement list stops and the image starts

Paymenter publishes no install command in its README. It sends you to two different documentation pages instead: the navigation row links https://paymenter.org/docs/installation/install, and the Getting Started section links https://paymenter.org/docs/getting-started/introduction/. The requirement list names PHP 8.3 or higher, Composer, a webserver running Apache or Nginx, and MariaDB, and the last push on the master branch landed on 2026-09-23.

Everything after that list lives inside the Dockerfile, which pulls the Composer installer from getcomposer.org and then installs dependencies twice, once before the application files are copied and once after. Those three lines are the whole PHP side of an install:

bash
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
RUN composer install --no-dev --no-autoloader --no-scripts
RUN composer install --no-dev --optimize-autoloader

The first line puts the composer binary in /usr/local/bin, the second resolves dependencies from composer.json and composer.lock with no scripts and no autoloader so the layer stays cacheable, and the third runs again after the source lands and writes the optimized autoloader. Note what is missing from all of it: no artisan command appears in these files apart from schedule:run, so how the database schema is created and later upgraded is not something the README answers.

The example environment assumes Redis the requirements never mention

The shipped .env.example describes what the application expects at run time, and it does not line up with the four item requirement list. Database traffic goes to MariaDB on port 3306 with both the database name and the user set to paymenter, while CACHE_STORE=redis and QUEUE_CONNECTION=redis send the cache and the job queue to a Redis server on REDIS_PORT=6379 through REDIS_CLIENT=phpredis. The image does prepare for that, installing the client with pecl install redis and enabling it with docker-php-ext-enable redis, even though the requirements stop at MariaDB.

bash
DB_CONNECTION=mariadb
DB_PORT=3306
DB_DATABASE=paymenter
DB_USERNAME=paymenter
CACHE_STORE=redis
QUEUE_CONNECTION=redis
REDIS_CLIENT=phpredis
REDIS_PORT=6379
SESSION_LIFETIME=120
BCRYPT_ROUNDS=12

Two other defaults deserve a closer look. MEMCACHED_HOST=127.0.0.1 sits in the same file while CACHE_STORE names redis, so nothing in the shipped defaults selects memcached and the host line is carried along beside it. SESSION_LIFETIME=120 and BCRYPT_ROUNDS=12 set the session length and the password hashing cost, while APP_ENV=production with APP_DEBUG=false and LOG_STACK=daily means a misconfigured deployment shows a visitor nothing useful and leaves you a daily log file to read.

Cron inside the container carries the scheduled work

A billing panel has to age subscriptions on a schedule, and Paymenter's image wires that to a shell cron job instead of a long running worker. The final stage installs dcron next to nginx and supervisor, then writes a single entry into the root crontab while the image is being built:

bash
* * * * * /usr/local/bin/php /app/artisan schedule:run >> /dev/null 2>&1

Read what that line does not say. Both streams are redirected to /dev/null, so a failing run leaves no trace in the container output, and the daily file driven by LOG_CHANNEL=stack and LOG_STACK=daily is where a failure would have to show up instead. The job fires once a minute, and no scheduled task it is meant to trigger is named in the README, so what this buys you is visible only after you register something. Install by hand and skip cron and nothing schedules at all, and the README does not warn you about that gap.

Startup copies themes and extensions back to their defaults

The production stage prepares for restarts by duplicating two directories before the container serves a request. It copies /app/themes to /app/themes_default and /app/extensions to /app/extensions_default, and a comment in the file calls this renewal on startup while pointing at PAYMENTER_SKIP_DEFAULT=true as the way to skip it.

dockerfile
RUN cp -r /app/themes /app/themes_default && \
    cp -r /app/extensions /app/extensions_default

Read that as a restore rather than a one time seed: with the variable unset, a file you mount over themes/ or extensions/ is overwritten on the next boot. Extensions are the extension point of the whole design, and the repository keeps extensions/ and themes/ at its root while crowdin.yml and a lang/ directory show that interface strings travel through Crowdin. The topic list names pterodactyl and stripe, yet the README names no integration for either and no extension list ships with the core, so which module covers a given provisioning system gets decided outside these files.

The image deletes .env and leaves storage at 777

One build step decides how much configuration you get to keep. The image copies .env.example to .env, sets bootstrap and storage to world writable permissions, deletes .env and the cached PHP files under bootstrap/cache, hands the tree to the nginx user, and removes the stock php-fpm.conf so a custom pool file can replace it.

dockerfile
RUN cp .env.example .env \
    && chmod 777 -R bootstrap storage/* \
    && rm -rf .env bootstrap/cache/*.php \
    && chown -R nginx:nginx . \

That has a direct effect on a first run: no .env survives inside the image, so every key, APP_KEY included, has to arrive from your compose file or orchestrator, and the empty APP_KEY line in .env.example is a reminder that nothing here fills it in for you. The wide permissions are the other half of that arrangement, since writable storage and bootstrap are what let the nginx user write logs and compiled views without matching user ids by hand. update.sh sits at the repository root as the only upgrade entry point named anywhere in these files, and the README does not document a rollback or a migration step to accompany it.

What is left for the operator to verify

Two deployment routes exist, and they differ more than the documentation lets on. The container route pins php:8.3-fpm-alpine and node:22-alpine, carries nginx inside the same image, and fixes the PHP extension set at bcmath, gd, pdo_mysql, zip, intl, sockets, gmp and redis, so changing any of them means editing the Dockerfile and rebuilding. The manual route described by the requirement list lets you choose versions, extensions and the webserver yourself, but then the nginx configuration, the PHP-FPM pool, the cron entry and the composer install all become yours to maintain. The repository also ships docker-compose.example.yml beside docker-compose.beta.yml without saying which one to copy or what beta covers.

The code is offered under the MIT licence, so a hosting company can adapt the panel and redistribute those changes without a separate agreement, and nothing in the repository addresses how a modified core interacts with an extension pulled from the marketplace. Before committing to a deployment, check whether an extension exists for your provisioning system, whether APP_KEY and the Redis settings are supplied outside the image, and what update.sh does before a real customer touches the panel.

Editorial conclusion

Paymenter suits a hosting reseller who wants recurring billing in one self-hosted panel and can run PHP 8.3 on MariaDB with Redis behind the cache and queue. It does not suit a general merchandise store, and it documents no upgrade or rollback path. The container route pins your PHP version, extension set, webserver and cron entry, so a custom stack becomes your maintenance job. Confirm an extension exists for your provisioning system, then check that APP_KEY, the Redis settings and PAYMENTER_SKIP_DEFAULT survive a restart.

Frequently asked questions

What does Paymenter need in order to run?

The stated requirements are PHP 8.3 or higher, Composer, a webserver running Apache or Nginx, and MariaDB. The container image adds more than that: Node 22 for the asset build, Redis for cache and queue, and dcron for the scheduler.

Is Paymenter actively maintained?

The repository is not archived, and the last push on the master branch landed on 2026-09-23. Tagged builds have appeared through August 2026, so the project was still moving when that push landed.

Will a container restart wipe my custom themes or extensions?

Without PAYMENTER_SKIP_DEFAULT=true, the production stage copies themes_default and extensions_default back over those directories on startup. Set the variable to keep your own files in place.

Does Paymenter support Pterodactyl or Stripe out of the box?

Both appear in the repository topic list, but the README names no integration for either and no extension ships with the core. The README points to the extension catalogue at https://paymenter.org/marketplace instead.

Which licence is Paymenter under?

MIT, stated in the repository description and in the LICENSE file the README links from the badge. The README phrases it as freedom to adapt and evolve the platform for your own requirements.

Official sources

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

Was this useful?