A small, self-maintaining Docker image for Spotweb,
built on nginx + PHP-FPM (Alpine). It tracks the upstream develop branch and rebuilds only
when upstream actually changes. Deploy files are included for Coolify and for a standalone host
such as a Synology NAS.
The goal is a set-and-forget setup: new upstream commits are picked up, tested and published automatically, and you are only notified when the build breaks.
Published to both registries, multi-arch (linux/amd64, linux/arm64):
ghcr.io/istoutjesdijk/spotwebdocker.io/istoutjesdijk/spotweb
Tags:
latest— the most recent successful build.sha-<shortsha>— the upstream Spotweb commit the image was built from.YYYYMMDD— the build date, useful for pinning and rollback.
Work lands on develop, which is the default branch. main only ever holds commits whose image
has been built, tested and published, and it is the branch Coolify deploys.
upstream-updateruns daily. It compares the upstreamdevelopHEAD against the commit pinned in.upstream-ref. If they differ, it commits the new commit id todevelop.- A push to
developstartsbuild, which builds the image, runs a smoke test, and — only if the test passes — publishes the new image to both registries. It then fast-forwardsmainto the same commit. - Builds run one at a time, so
latestalways matches the commitmainpoints at. - If a build fails, the workflow run is marked failed. GitHub emails the repository owner, and
the workflow also opens (or comments on) a
ci-failureissue. Nothing is published andmaindoes not move, solatestand the deployment keep running the last working image. - Dependabot keeps the workflow actions and the Alpine base image current with monthly pull
requests against
develop.
GitHub does not let the workflow's own token push changes to files under .github/workflows/,
not even as a fast-forward. A commit that changes a workflow therefore builds and publishes as
usual, but the "Promote to main" step fails. Move main by hand once the build is green:
git push origin develop:mainNever commit to main directly. A commit there deploys without having been built or tested, and
the next promotion fails because main is no longer behind develop.
On the deployment side, updates are pulled in one of two ways:
- Standalone with Watchtower: Watchtower polls the registry and pulls + restarts Spotweb when a new image appears.
- Coolify: auto-deploy on the
mainbranch. The push that movesmainarrives after the image is published, so the redeploy picks up the newlatest.
cp .env.example .env # edit MYSQL_PASSWORD at least
docker compose -f deploy/standalone/docker-compose.yml up -dThe UI is served on http://<host>:8081 by default (change SPOTWEB_PORT in .env). This
variant bundles MariaDB and does not update itself.
For automatic image updates, use the Watchtower variant instead:
docker compose -f deploy/standalone/docker-compose.watchtower.yml up -dOn Synology, import either compose file in Container Manager as a new project, place the values
from .env.example in the project's environment, and start it.
The Coolify stack contains only Spotweb and connects to a Coolify-managed database.
- Create a Resource → Database → MariaDB. Note its host, database name, user and password.
- Create a Resource → Docker Compose and paste
deploy/coolify/docker-compose.yml. - In the resource's environment variables, set
SPOTWEB_DB_HOST,SPOTWEB_DB_NAME,SPOTWEB_DB_USERandSPOTWEB_DB_PASSto the database values.SERVICE_FQDN_SPOTWEB_8080makes Coolify assign a domain and proxy HTTPS to port 8080. - Deploy.
Health check: the image exposes /healthz, served directly by nginx, and the compose file
declares a matching healthcheck. Coolify uses it to mark the resource healthy once the web
server is up, independent of database state.
Auto-deploy: point the resource at the main branch with auto-deploy on. Coolify then redeploys
whenever build promotes a tested commit to main, which happens only after its image is
published. Do not point it at develop: that branch moves before the image exists, so Coolify
would pull the previous latest.
The container creates the database schema and a default admin account automatically on first
start, so you do not need the install.php wizard.
- Default login: username
admin, passwordadmin. Change the password immediately under Settings after logging in. - Configure a Usenet (NNTP) provider under Settings before spots can be retrieved.
To reset the admin password from the host if needed:
docker compose exec spotweb su-exec www-data php /var/www/spotweb/bin/upgrade-db.php --reset-password adminThis sets it back to spotweb.
| Variable | Default | Description |
|---|---|---|
TZ |
Etc/UTC |
Container timezone. |
SPOTWEB_DB_TYPE |
pdo_mysql |
Database engine: pdo_mysql or pdo_pgsql. |
SPOTWEB_DB_HOST |
— | Database host. |
SPOTWEB_DB_PORT |
engine default | Database port (3306 for MySQL, 5432 for PostgreSQL). |
SPOTWEB_DB_NAME |
— | Database name. |
SPOTWEB_DB_USER |
— | Database user. |
SPOTWEB_DB_PASS |
— | Database password. |
SPOTWEB_CRON_RETRIEVE |
*/15 * * * * |
Cron schedule for retrieving spots. Empty disables it. |
SPOTWEB_CRON_CACHE_CHECK |
0 4 * * * |
Cron schedule for the cache check. Empty disables it. |
SPOTWEB_FORCE_HTTPS |
false |
Generate https:// URLs regardless of what the proxy reports. See below. |
A /config volume is mounted for persistence. If it contains dbsettings.inc.php or
ownsettings.php, those files take precedence over the environment variables.
Spotweb builds absolute URLs from the scheme the proxy reports in X-Forwarded-Proto,
including the <base href> on every page. A proxy that sets that header correctly needs no
configuration here.
Set SPOTWEB_FORCE_HTTPS=true when TLS is terminated further out than the proxy that talks to
the container, so it forwards X-Forwarded-Proto: http while the browser is on https. A
Cloudflare Tunnel in front of a Coolify http:// domain does exactly that: Traefik replaces the
header the tunnel sent, Spotweb emits http:// asset URLs, and the browser blocks them as mixed
content. Forcing the scheme fixes it without changing the proxy chain.
- Standalone: back up the
spotweb-dbvolume (the database holds all spots, comments and settings) and, if you customised it,spotweb-config. - Coolify: use the managed database resource's backup feature.
Images publish to GHCR out of the box using the built-in GITHUB_TOKEN; no secret is required.
Set the spotweb package visibility to public in the package settings if you want
unauthenticated pulls.
To also publish to Docker Hub (optional), add under Settings → Secrets and variables → Actions:
- Variable
DOCKERHUB_USERNAME— your Docker Hub username. - Secret
DOCKERHUB_TOKEN— a Docker Hub access token with write access.
Without these, the build still succeeds and publishes to GHCR only.
The workflows request the permissions they need at the job level, so the repository-wide Actions "Workflow permissions" setting does not need to be changed from its default.
- Watchtower and
:latestdeployments update on their own. - To pin a specific version, use a
YYYYMMDDorsha-<shortsha>tag as the image instead oflatest. - To roll back, deploy an earlier date tag; every published build stays available.
docker build -t spotweb:test .
SPOTWEB_IMAGE=spotweb:test ./test/smoke.shThe smoke test boots the standalone stack, waits for the container to become healthy, and checks
/healthz, the main page and a clean schema bootstrap.
Spotweb is developed at spotweb/spotweb. This repository only packages and maintains a container image around it.