Install with Docker
Run Tidemark Billing from the published image with SQLite or MariaDB, and keep your data in a volume.
The Docker image runs the web server, the scheduler and a queue worker in one container. Everything that changes lives in the /data volume.
Start it with SQLite#
-
Save the project's
docker-compose.yml, or use this:services: billing: image: ghcr.io/tidemark-apps/billing:latest restart: unless-stopped ports: - "8080:8080" volumes: - billing-data:/data environment: APP_URL: http://localhost:8080 volumes: billing-data: -
Run
docker compose up -d. -
Open
http://localhost:8080/installand create your admin account.
Set APP_URL to the address customers will use. It appears in emails, invoice links and webhook URLs. The project README lists the image tags latest, a minor version such as 0.1, and exact versions such as 0.1.0, for amd64 and arm64.
Skip the installer#
If you set BILLING_ADMIN_EMAIL and BILLING_ADMIN_PASSWORD, the container creates the admin account on its first start, as long as the installation has not run before. You can also set BILLING_COMPANY and BILLING_CURRENCY (default USD), and BILLING_ADMIN_NAME (default Administrator).
Remove the password from your compose file once the account exists.
Use MariaDB#
The project ships docker-compose.mysql.yml, which adds a mariadb:11 container.
- Edit the
change-mepasswords in the file. - Run
docker compose -f docker-compose.mysql.yml up -d. - Open
http://localhost:8080/install, choose MySQL / MariaDB, and enter hostdb, databasebillingand userbilling, plus the password you set.
The database files live in the separate db-data volume.
What is in the data volume#
Path in /data |
Contents |
|---|---|
.env |
Settings file, created on first start with a generated APP_KEY |
database.sqlite |
The SQLite database (created empty even if you use MariaDB) |
storage/ |
Uploads, attachments, logs, cache and sessions |
plugins/ |
Plugins you install yourself |
Plugins bundled with the image are in the image. Plugins you add or upload go in /data/plugins, which the app scans too.
What runs inside#
- The web server listens on port 8080.
- A loop runs
php artisan schedule:runevery 60 seconds, so you do not need a cron job. - A queue worker handles provisioning and webhook deliveries.
- Logs go to the container's standard error, so
docker compose logs billingshows them. - A health check requests
/upevery 30 seconds.
File ownership#
The container starts as root only to give /data to the user in PUID and PGID (both default to 1000), then drops to that user. On Unraid, use PUID=99 and PGID=100. If you start the container as a non-root user with docker --user, this step is skipped.