Install with Docker

Tidemark is also published as a Docker image, for people who run their own servers or homelab and would rather not set up PHP. The image runs the app and its scheduler in one container, and works on both amd64 and arm64 (including Raspberry Pi). If you are on shared hosting, use the shared hosting install instead.

Quick start

You need Docker with Compose. Save this as docker-compose.yml  :

services:
  tidemark:
    image: ghcr.io/tidemark-analytics/tidemark:latest
    restart: unless-stopped
    ports:
      - "8080:8080"
    volumes:
      - tidemark-data:/data
    environment:
      APP_URL: https://stats.example.com

volumes:
  tidemark-data:

Start it, then open the installer to create your admin account:

docker compose up -d
# then visit http://localhost:8080/install

Set APP_URL   to the address visitors and you will use to reach Tidemark. It appears in the tracking snippet and in links. The rest is the same as any install: add a site, paste the snippet into your site's <head>  , and the dashboard opens when the first pageview arrives. See The tracking snippet.

What is in the image

  • The app is served by FrankenPHP (Caddy with PHP 8.4) on port 8080. It speaks plain HTTP, so put it behind a reverse proxy for HTTPS.
  • The scheduler runs inside the container, so you don't need a cron job. It drains the ingest queue, applies data retention and checks for new versions.
  • Everything that changes lives in the /data  volume: the settings file (.env  ), the SQLite database, logs and uploads. The container itself can be deleted and recreated at any time.
  • The app runs as a non-root user (uid 1000). Logs go to the container's output: docker compose logs -f tidemark  .
  • The in-app updater is switched off, because the image is read-only by design. To update, pull a newer image (see below).

HTTPS and a reverse proxy

Run Tidemark behind the proxy you already use (Caddy, Traefik, nginx, Nginx Proxy Manager or a Cloudflare Tunnel) and forward your domain to port 8080. Tidemark trusts the proxy's forwarded headers, so it sees the visitor's real address and uses HTTPS links.

A minimal Caddy example:

stats.example.com {
    reverse_proxy tidemark:8080
}

Visitor countries come from a CDN's headers, such as Cloudflare's, exactly as on any other install. Without one, the dashboard shows visits but not where they came from.

Using MySQL, MariaDB or PostgreSQL

SQLite is the default and is fine for most sites. For a bigger site you can use another database. Add a database container and choose it in the installer. For example, with MariaDB:

services:
  db:
    image: mariadb:11
    restart: unless-stopped
    environment:
      MARIADB_ROOT_PASSWORD: change-me-root
      MARIADB_DATABASE: tidemark
      MARIADB_USER: tidemark
      MARIADB_PASSWORD: change-me
    volumes:
      - db-data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 5s
      retries: 20

  tidemark:
    image: ghcr.io/tidemark-analytics/tidemark:latest
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    ports:
      - "8080:8080"
    volumes:
      - tidemark-data:/data
    environment:
      APP_URL: https://stats.example.com

volumes:
  db-data:
  tidemark-data:

In the installer choose MySQL / MariaDB and enter host db  , port 3306  , database tidemark  , user tidemark   and the password you set. For PostgreSQL use the postgres   image the same way, with the PostgreSQL option and port 5432  .

Settings

Every setting in Configuration can be given as an environment variable on the container, for example outgoing email:

    environment:
      APP_URL: https://stats.example.com
      MAIL_MAILER: smtp
      MAIL_HOST: smtp.example.com
      MAIL_PORT: "587"
      MAIL_USERNAME: tidemark
      MAIL_PASSWORD: your-password
      MAIL_FROM_ADDRESS: stats@example.com
      TIDEMARK_RETENTION_DAYS: "730"

Environment variables win over the settings file in the /data   volume. See Email for what Tidemark sends.

Updating

  1. Back up first. See the Backups section below.
  2. Pull the new image and recreate the container:
docker compose pull
docker compose up -d
  1. Open the dashboard. Database migrations run automatically on the first request after an update.

The latest   tag follows every release. If you would rather choose when to upgrade, pin a version such as ghcr.io/tidemark-analytics/tidemark:0.14.0  , or :0.14   to follow only that release line's fixes. Release notes are on GitHub, and Tidemark shows a dot in the dashboard when a newer version exists.

Backups

With SQLite, everything is in the /data   volume. Stop the container, copy the volume, then start it again:

docker compose stop tidemark
docker run --rm -v tidemark_tidemark-data:/data -v "$PWD":/backup alpine tar czf /backup/tidemark-data.tgz -C /data .
docker compose start tidemark

The volume's real name is your project folder name plus _tidemark-data  ; docker volume ls   shows it. With MariaDB, MySQL or PostgreSQL, back up the database with its own dump tool (mariadb-dump   or pg_dump  ) and also keep a copy of /data/.env  , which holds your app key. More on what to keep in Backups.

Using a folder instead of a volume

If you prefer a bind mount such as ./tidemark-data:/data  , Tidemark takes ownership of the folder when it starts, so no setup is needed. To run as a different user (Unraid uses 99 and 100), set these environment variables:

PUID: "99"
PGID: "100"

Troubleshooting

  • The container restarts or shows unhealthy. Read the log with docker compose logs tidemark  . A permission error on /data   almost always means a bind-mounted folder isn't writable by uid 1000.
  • The installer can't reach the database. Use the database container's service name (such as db  ) as the host, not localhost  , and wait for the database to become healthy before installing.
  • The tracking snippet or links show the wrong address. Set APP_URL   to your public address and recreate the container.
  • Visitors all show one address. Check that your reverse proxy forwards the client address in the usual headers. Tidemark already trusts them.
  • Everything else. See Troubleshooting, and ask on GitHub.

Moving between Docker and a shared-hosting install is a download and an upload: see Moving between installs.