From b1b01253150c49b9018790a9cd52248927bbab82 Mon Sep 17 00:00:00 2001 From: Daniel Esser Date: Thu, 16 Jul 2026 08:55:20 +0200 Subject: [PATCH 01/12] Restructure Docker Compose setup around env-var driven configuration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The single-file quick start still works with zero edits, but secrets and settings now have working defaults baked in via ${VAR:-default} instead of being hardcoded. To change one, place a .env file next to docker-compose.yml and set the variable there — see .env.example — rather than hunting down every place it appears in the compose file. Also adds restart: unless-stopped to all services and bumps mongo to the latest 7.0.x patch release. Co-Authored-By: Claude Sonnet 5 --- .env.example | 20 ++++++++++++++++++++ .gitignore | 1 + README.md | 4 ++++ docker-compose.yml | 32 ++++++++++++++++++++------------ 4 files changed, 45 insertions(+), 12 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e078715 --- /dev/null +++ b/.env.example @@ -0,0 +1,20 @@ +# Copy this file to `.env` (same directory as docker-compose.yml) and adjust +# the values you want to change. Anything left out keeps the default baked +# into docker-compose.yml. +# +# Changing a value here updates it everywhere it's used — e.g. +# MONGO_ROOT_PASSWORD is shared by the api and mongodb services, so you only +# need to change it once, right here. + +# MongoDB root credentials, used by both the api and mongodb services. +# Changing MONGO_ROOT_PASSWORD after the database was initialized doesn't +# rotate it automatically — see docs.nodepit.com/runner/configuration. +MONGO_ROOT_USERNAME=root +MONGO_ROOT_PASSWORD=drnFEtX_PWKizUzKr3KT + +# Shared secret between the api and executor services. +API_KEY=nYCA3oaqhYJ__GEteWye + +# The URL Runner is reachable at. Change for anything beyond local use, e.g. +# https://npr.example.com — required when using the nginx extension. +WEB_BASE_URL=http://localhost:8080 diff --git a/.gitignore b/.gitignore index 8000dd9..f910599 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .vagrant +.env diff --git a/README.md b/README.md index 9a27f8b..8acf740 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,10 @@ NodePit Runner is licensed under the [NodePit Runner: Terms and Conditions](http
Show more + ## Changing secrets and settings + + `docker-compose.yml` works out of the box with built-in default secrets. To change one — e.g. rotate the MongoDB password or set a public `WEB_BASE_URL` — clone this repo, copy [`.env.example`](.env.example) to `.env`, and edit the values there. Because `.env` is read by every service that needs a given value, you only need to change it in one place. + ## Vagrant If you use [Vagrant](https://developer.hashicorp.com/vagrant), there’s a [Vagrantfile](Vagrantfile) to run a Debian box with Docker preinstalled. Start and connect to the box as follows: diff --git a/docker-compose.yml b/docker-compose.yml index 1a3bf0f..1de1193 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,11 @@ # NodePit Runner — Docker Compose file # -# Four services: api, web, executor and mongodb. Edit the values marked below, -# then run `docker compose up -d` to start the stack. +# Four services: api, web, executor and mongodb. Works out of the box with +# `docker compose up -d` — no editing required. +# +# All secrets and settings below have working defaults baked in via +# `${VAR:-default}`. To change one (e.g. the shared API key), place a `.env` +# file next to this one and set the variable there — see `.env.example`. # # Docs: # Installation https://docs.nodepit.com/runner/installation @@ -21,21 +25,23 @@ x-logging: services: api: image: ghcr.io/nodepit/runner-api:1 + restart: unless-stopped volumes: - api-data:/nodepit environment: - # Must match MONGO_INITDB_ROOT_USERNAME/PASSWORD on the mongodb service below. - DB_URL: mongodb://root:drnFEtX_PWKizUzKr3KT@mongodb/nodepit-runner?authSource=admin + # Built from MONGO_ROOT_USERNAME/MONGO_ROOT_PASSWORD below. + DB_URL: mongodb://${MONGO_ROOT_USERNAME:-root}:${MONGO_ROOT_PASSWORD:-drnFEtX_PWKizUzKr3KT}@mongodb/nodepit-runner?authSource=admin # Shared secret with API_KEY on the executor service below. - API_KEY: nYCA3oaqhYJ__GEteWye + API_KEY: ${API_KEY:-nYCA3oaqhYJ__GEteWye} # The URL Runner is reachable at. Change for anything beyond local use. - WEB_BASE_URL: http://localhost:8080 + WEB_BASE_URL: ${WEB_BASE_URL:-http://localhost:8080} depends_on: - mongodb logging: *default-logging web: image: ghcr.io/nodepit/runner-web:1 + restart: unless-stopped ports: - 8080:80 environment: @@ -46,13 +52,14 @@ services: executor: image: ghcr.io/nodepit/runner-executor:1 + restart: unless-stopped volumes: - /var/run/docker.sock:/var/run/docker.sock - executor-data:/opt/data environment: API_BASE_URL: http://api:3000 # Shared secret with API_KEY on the api service above. - API_KEY: nYCA3oaqhYJ__GEteWye + API_KEY: ${API_KEY:-nYCA3oaqhYJ__GEteWye} depends_on: - api deploy: @@ -61,14 +68,15 @@ services: logging: *default-logging mongodb: - image: mongo:7.0.31 + image: mongo:7.0.37 + restart: unless-stopped volumes: - mongodb-data:/data/db environment: - # Must match the username/password in DB_URL on the api service above. - # Only used when initializing a new database — see the docs to change it later. - MONGO_INITDB_ROOT_USERNAME: root - MONGO_INITDB_ROOT_PASSWORD: drnFEtX_PWKizUzKr3KT + # Only used when initializing a new database — see the docs to change + # the password later. + MONGO_INITDB_ROOT_USERNAME: ${MONGO_ROOT_USERNAME:-root} + MONGO_INITDB_ROOT_PASSWORD: ${MONGO_ROOT_PASSWORD:-drnFEtX_PWKizUzKr3KT} logging: *default-logging volumes: From a34c023f7401686a0e8e7074322c028fe67298b2 Mon Sep 17 00:00:00 2001 From: Daniel Esser Date: Thu, 16 Jul 2026 08:58:57 +0200 Subject: [PATCH 02/12] Add nginx extension for HTTPS reverse proxy with automatic certificates Adds nginx-proxy plus acme-companion for automatic Let's Encrypt certificate issuance, layered on with -f as an opt-in overlay rather than a separate combined compose file. Requires DOMAIN and LETSENCRYPT_EMAIL in .env, enforced by Compose's ${VAR:?message} syntax so it fails fast with a clear message instead of starting misconfigured. Co-Authored-By: Claude Sonnet 5 --- .env.example | 7 +++ .gitignore | 1 + README.md | 13 +++++ docker-compose.yml | 2 + .../nginx/conf.d/client_max_body_size.conf | 3 ++ extensions/nginx/docker-compose.yml | 50 +++++++++++++++++++ 6 files changed, 76 insertions(+) create mode 100644 extensions/nginx/conf.d/client_max_body_size.conf create mode 100644 extensions/nginx/docker-compose.yml diff --git a/.env.example b/.env.example index e078715..c91ec90 100644 --- a/.env.example +++ b/.env.example @@ -18,3 +18,10 @@ API_KEY=nYCA3oaqhYJ__GEteWye # The URL Runner is reachable at. Change for anything beyond local use, e.g. # https://npr.example.com — required when using the nginx extension. WEB_BASE_URL=http://localhost:8080 + +# --- extensions/nginx --- +# Public domain name, must match WEB_BASE_URL above and have a DNS entry +# pointing at this server. +# DOMAIN=npr.example.com +# Email address used for Let's Encrypt certificate registration/renewal. +# LETSENCRYPT_EMAIL=admin@example.com diff --git a/.gitignore b/.gitignore index f910599..4f1ef62 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .vagrant .env +extensions/nginx/conf.d/default.conf diff --git a/README.md b/README.md index 8acf740..c743f97 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,19 @@ To upgrade an already running instance, run `docker compose pull` followed by `d The full manual — installation, configuration, guides and the API reference — lives at [**docs.nodepit.com/runner**](https://docs.nodepit.com/runner). +## 🧩 Extensions + +The [`extensions/`](extensions/) directory has optional add-ons, each a self-contained Compose file you layer on top of `docker-compose.yml` with `-f`. Some need their own settings — copy [`.env.example`](.env.example) to `.env` (next to `docker-compose.yml`) and fill in what’s needed: + +* [`extensions/nginx`](extensions/nginx) — HTTPS reverse proxy with automatic Let’s Encrypt certificates. + +```shell +docker compose \ + -f docker-compose.yml \ + -f extensions/nginx/docker-compose.yml \ + up -d +``` + ## 🤗 Get Involved Unsure if NodePit Runner is for you? Drop us a [mail](mailto:mail@nodepit.com) and we answer your questions and even better get you access to our cloud version of NodePit Runner for testing. diff --git a/docker-compose.yml b/docker-compose.yml index 1de1193..97e92b7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,6 +7,8 @@ # `${VAR:-default}`. To change one (e.g. the shared API key), place a `.env` # file next to this one and set the variable there — see `.env.example`. # +# Want HTTPS? See the `extensions/` directory and the README. +# # Docs: # Installation https://docs.nodepit.com/runner/installation # Configuration https://docs.nodepit.com/runner/configuration diff --git a/extensions/nginx/conf.d/client_max_body_size.conf b/extensions/nginx/conf.d/client_max_body_size.conf new file mode 100644 index 0000000..9950721 --- /dev/null +++ b/extensions/nginx/conf.d/client_max_body_size.conf @@ -0,0 +1,3 @@ +# disable nginx file upload limit which is per default `1m` +# https://nginx.org/en/docs/http/ngx_http_core_module.html +client_max_body_size 0; diff --git a/extensions/nginx/docker-compose.yml b/extensions/nginx/docker-compose.yml new file mode 100644 index 0000000..36c8f3f --- /dev/null +++ b/extensions/nginx/docker-compose.yml @@ -0,0 +1,50 @@ +# NodePit Runner — nginx extension +# +# Adds an HTTPS reverse proxy (nginx-proxy) in front of the web/api services, +# with automatic Let's Encrypt certificates via acme-companion. +# +# Requires DOMAIN and LETSENCRYPT_EMAIL to be set in .env (see +# .env.example), and a DNS entry pointing DOMAIN at this server. Also set +# WEB_BASE_URL=https:// in .env so links generated by Runner use the +# public HTTPS URL. +# +# Usage: +# docker compose -f docker-compose.yml -f extensions/nginx/docker-compose.yml up -d + +services: + web: + environment: + VIRTUAL_HOST: ${DOMAIN:?Set DOMAIN in .env before using the nginx extension — see .env.example} + LETSENCRYPT_HOST: ${DOMAIN:?Set DOMAIN in .env before using the nginx extension — see .env.example} + LETSENCRYPT_EMAIL: ${LETSENCRYPT_EMAIL:?Set LETSENCRYPT_EMAIL in .env before using the nginx extension — see .env.example} + + nginx-proxy: + image: nginxproxy/nginx-proxy:1.11.5 + restart: unless-stopped + ports: + - 80:80 + - 443:443 + volumes: + - certs:/etc/nginx/certs + - vhost:/etc/nginx/vhost.d + - html:/usr/share/nginx/html + - /var/run/docker.sock:/tmp/docker.sock:ro + - ./extensions/nginx/conf.d:/etc/nginx/conf.d + labels: + - com.github.nginx-proxy.nginx=true + + acme-companion: + image: nginxproxy/acme-companion:2.8.1 + restart: unless-stopped + volumes: + - certs:/etc/nginx/certs + - vhost:/etc/nginx/vhost.d + - html:/usr/share/nginx/html + - acme:/etc/acme.sh + - /var/run/docker.sock:/var/run/docker.sock:ro + +volumes: + certs: + vhost: + html: + acme: From 4dbbc0c216c950fbef715c69b62f49be609c0961 Mon Sep 17 00:00:00 2001 From: Daniel Esser Date: Thu, 16 Jul 2026 09:00:25 +0200 Subject: [PATCH 03/12] Add watchtower extension for automatic container updates Uses the nickfedor/watchtower fork rather than containrrr/watchtower, which is deprecated. Labels api, web, and executor with com.centurylinklabs.watchtower.enable so only Runner's own containers are targeted, not the extension containers or anything else on the host. Co-Authored-By: Claude Sonnet 5 --- README.md | 14 ++++++++++++ docker-compose.yml | 3 ++- extensions/watchtower/docker-compose.yml | 27 ++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 extensions/watchtower/docker-compose.yml diff --git a/README.md b/README.md index c743f97..7e395a8 100644 --- a/README.md +++ b/README.md @@ -47,11 +47,15 @@ The full manual — installation, configuration, guides and the API reference The [`extensions/`](extensions/) directory has optional add-ons, each a self-contained Compose file you layer on top of `docker-compose.yml` with `-f`. Some need their own settings — copy [`.env.example`](.env.example) to `.env` (next to `docker-compose.yml`) and fill in what’s needed: * [`extensions/nginx`](extensions/nginx) — HTTPS reverse proxy with automatic Let’s Encrypt certificates. +* [`extensions/watchtower`](extensions/watchtower) — automatically pulls and restarts containers when new images are released. + +Mix and match as needed, for example: ```shell docker compose \ -f docker-compose.yml \ -f extensions/nginx/docker-compose.yml \ + -f extensions/watchtower/docker-compose.yml \ up -d ``` @@ -75,6 +79,16 @@ NodePit Runner is licensed under the [NodePit Runner: Terms and Conditions](http `docker-compose.yml` works out of the box with built-in default secrets. To change one — e.g. rotate the MongoDB password or set a public `WEB_BASE_URL` — clone this repo, copy [`.env.example`](.env.example) to `.env`, and edit the values there. Because `.env` is read by every service that needs a given value, you only need to change it in one place. + ## Combining extensions without repeating `-f` flags + + Instead of passing multiple `-f` flags every time, set `COMPOSE_FILE` in `.env`: + + ``` + COMPOSE_FILE=docker-compose.yml:extensions/nginx/docker-compose.yml:extensions/watchtower/docker-compose.yml + ``` + + and then just run `docker compose up -d`. + ## Vagrant If you use [Vagrant](https://developer.hashicorp.com/vagrant), there’s a [Vagrantfile](Vagrantfile) to run a Debian box with Docker preinstalled. Start and connect to the box as follows: diff --git a/docker-compose.yml b/docker-compose.yml index 97e92b7..5312c7c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,7 +7,8 @@ # `${VAR:-default}`. To change one (e.g. the shared API key), place a `.env` # file next to this one and set the variable there — see `.env.example`. # -# Want HTTPS? See the `extensions/` directory and the README. +# Want HTTPS or automatic updates? See the `extensions/` directory and the +# README. # # Docs: # Installation https://docs.nodepit.com/runner/installation diff --git a/extensions/watchtower/docker-compose.yml b/extensions/watchtower/docker-compose.yml new file mode 100644 index 0000000..afb5820 --- /dev/null +++ b/extensions/watchtower/docker-compose.yml @@ -0,0 +1,27 @@ +# NodePit Runner — watchtower extension +# +# Automatically keeps the NodePit Runner containers up to date by pulling +# and restarting them on a daily interval. +# +# Usage: +# docker compose -f docker-compose.yml -f extensions/watchtower/docker-compose.yml up -d + +services: + api: + labels: + - com.centurylinklabs.watchtower.enable=true + + web: + labels: + - com.centurylinklabs.watchtower.enable=true + + executor: + labels: + - com.centurylinklabs.watchtower.enable=true + + watchtower: + image: nickfedor/watchtower:1.19.0 + restart: unless-stopped + volumes: + - /var/run/docker.sock:/var/run/docker.sock + command: --interval 86400 --label-enable --cleanup From ec617b68ebfe4fcf3b7420044df6762e80b10628 Mon Sep 17 00:00:00 2001 From: Daniel Esser Date: Thu, 16 Jul 2026 09:02:50 +0200 Subject: [PATCH 04/12] Add backup extension for database and workflow-data backups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Runs mongodump inside the mongodb container via docker-volume-backup's archive-pre/archive-post hooks, so the dump is consistent even while Runner is running, then archives it alongside the api-data volume (uploaded workflows and environments). executor-data is excluded — it's the executor's working/cache directory, not source-of-truth data. Backups land locally by default with zero configuration; shipping them to an S3-compatible bucket is opt-in via .env. Co-Authored-By: Claude Sonnet 5 --- .env.example | 12 +++++++ .gitignore | 1 + README.md | 1 + docker-compose.yml | 4 +-- extensions/backup/docker-compose.yml | 48 ++++++++++++++++++++++++++++ 5 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 extensions/backup/docker-compose.yml diff --git a/.env.example b/.env.example index c91ec90..db9c95f 100644 --- a/.env.example +++ b/.env.example @@ -25,3 +25,15 @@ WEB_BASE_URL=http://localhost:8080 # DOMAIN=npr.example.com # Email address used for Let's Encrypt certificate registration/renewal. # LETSENCRYPT_EMAIL=admin@example.com + +# --- extensions/backup --- +# Local backups (./extensions/backup/archive) and the daily schedule/ +# retention work without any of these being set. Set the following to also +# ship backups to an S3-compatible bucket: +# BACKUP_S3_BUCKET=my-npr-backups +# BACKUP_S3_ACCESS_KEY_ID= +# BACKUP_S3_SECRET_ACCESS_KEY= +# BACKUP_S3_ENDPOINT=s3.amazonaws.com +# BACKUP_S3_PATH= +# BACKUP_CRON_EXPRESSION=0 3 * * * +# BACKUP_RETENTION_DAYS=7 diff --git a/.gitignore b/.gitignore index 4f1ef62..7fd771e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .vagrant .env extensions/nginx/conf.d/default.conf +extensions/backup/archive/ diff --git a/README.md b/README.md index 7e395a8..8852f97 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ The [`extensions/`](extensions/) directory has optional add-ons, each a self-con * [`extensions/nginx`](extensions/nginx) — HTTPS reverse proxy with automatic Let’s Encrypt certificates. * [`extensions/watchtower`](extensions/watchtower) — automatically pulls and restarts containers when new images are released. +* [`extensions/backup`](extensions/backup) — daily database and workflow-data backups, stored locally and optionally shipped to an S3-compatible bucket. Mix and match as needed, for example: diff --git a/docker-compose.yml b/docker-compose.yml index 5312c7c..0d150bc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,8 +7,8 @@ # `${VAR:-default}`. To change one (e.g. the shared API key), place a `.env` # file next to this one and set the variable there — see `.env.example`. # -# Want HTTPS or automatic updates? See the `extensions/` directory and the -# README. +# Want HTTPS, automatic updates, or backups? See the `extensions/` directory +# and the README. # # Docs: # Installation https://docs.nodepit.com/runner/installation diff --git a/extensions/backup/docker-compose.yml b/extensions/backup/docker-compose.yml new file mode 100644 index 0000000..e7cca8d --- /dev/null +++ b/extensions/backup/docker-compose.yml @@ -0,0 +1,48 @@ +# NodePit Runner — backup extension +# +# Backs up the mongodb database (via mongodump, so it's consistent even +# while Runner is running) and the api-data volume (uploaded workflows and +# environments) on a daily schedule. The executor-data volume is excluded — +# it's the executor's working/cache directory, not source-of-truth data. +# +# By default, backups are written locally to ./extensions/backup/archive on +# the host. To also (or instead) ship them off-site, set the AWS_S3_* / S3_* +# variables in .env — see .env.example. Uses +# https://offen.github.io/docker-volume-backup/ under the hood. +# +# Usage: +# docker compose -f docker-compose.yml -f extensions/backup/docker-compose.yml up -d + +services: + mongodb: + volumes: + - mongodb-dump:/dump + labels: + docker-volume-backup.archive-pre: >- + /bin/sh -c 'mongodump --archive --gzip + --uri="mongodb://${MONGO_ROOT_USERNAME:-root}:${MONGO_ROOT_PASSWORD:-drnFEtX_PWKizUzKr3KT}@localhost/nodepit-runner?authSource=admin" + > /dump/mongodb.archive.gz' + docker-volume-backup.archive-post: "rm -f /dump/mongodb.archive.gz" + + backup: + image: offen/docker-volume-backup:v2.48.2 + restart: unless-stopped + environment: + BACKUP_CRON_EXPRESSION: ${BACKUP_CRON_EXPRESSION:-0 3 * * *} + BACKUP_FILENAME: npr-backup-%Y-%m-%dT%H-%M-%S.tar.gz + BACKUP_RETENTION_DAYS: ${BACKUP_RETENTION_DAYS:-7} + # Leave unset to skip shipping backups off-site — local backups under + # ./extensions/backup/archive still happen either way. + AWS_S3_BUCKET_NAME: ${BACKUP_S3_BUCKET:-} + AWS_S3_PATH: ${BACKUP_S3_PATH:-} + AWS_ACCESS_KEY_ID: ${BACKUP_S3_ACCESS_KEY_ID:-} + AWS_SECRET_ACCESS_KEY: ${BACKUP_S3_SECRET_ACCESS_KEY:-} + AWS_ENDPOINT: ${BACKUP_S3_ENDPOINT:-s3.amazonaws.com} + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - mongodb-dump:/backup/mongodb-dump:ro + - api-data:/backup/api-data:ro + - ./extensions/backup/archive:/archive + +volumes: + mongodb-dump: From 47eb3020b2ce01342585440a6148b97d23a2c3d5 Mon Sep 17 00:00:00 2001 From: Daniel Esser Date: Thu, 16 Jul 2026 09:04:17 +0200 Subject: [PATCH 05/12] Add portainer extension for web-based Docker stack management MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Zero-config, matching the watchtower extension's pattern — no secrets or .env vars needed. The admin account is claimed via Portainer's own one-time setup token on first visit instead of a baked-in default password. Co-Authored-By: Claude Sonnet 5 --- README.md | 1 + docker-compose.yml | 4 ++-- extensions/portainer/docker-compose.yml | 22 ++++++++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 extensions/portainer/docker-compose.yml diff --git a/README.md b/README.md index 8852f97..460415f 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ The [`extensions/`](extensions/) directory has optional add-ons, each a self-con * [`extensions/nginx`](extensions/nginx) — HTTPS reverse proxy with automatic Let’s Encrypt certificates. * [`extensions/watchtower`](extensions/watchtower) — automatically pulls and restarts containers when new images are released. * [`extensions/backup`](extensions/backup) — daily database and workflow-data backups, stored locally and optionally shipped to an S3-compatible bucket. +* [`extensions/portainer`](extensions/portainer) — a web UI for managing the Docker stack without the CLI. Mix and match as needed, for example: diff --git a/docker-compose.yml b/docker-compose.yml index 0d150bc..a2e9680 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,8 +7,8 @@ # `${VAR:-default}`. To change one (e.g. the shared API key), place a `.env` # file next to this one and set the variable there — see `.env.example`. # -# Want HTTPS, automatic updates, or backups? See the `extensions/` directory -# and the README. +# Want HTTPS, automatic updates, backups, or a Docker management UI? See the +# `extensions/` directory and the README. # # Docs: # Installation https://docs.nodepit.com/runner/installation diff --git a/extensions/portainer/docker-compose.yml b/extensions/portainer/docker-compose.yml new file mode 100644 index 0000000..9f10e8e --- /dev/null +++ b/extensions/portainer/docker-compose.yml @@ -0,0 +1,22 @@ +# NodePit Runner — portainer extension +# +# Adds Portainer CE, a web UI for managing the Docker stack (start/stop +# containers, view logs, inspect resource usage) without needing the CLI. +# On first visit, it asks you to create an admin account. +# +# Usage: +# docker compose -f docker-compose.yml -f extensions/portainer/docker-compose.yml up -d +# Open http://localhost:9000 + +services: + portainer: + image: portainer/portainer-ce:2.39.5 + restart: unless-stopped + ports: + - 9000:9000 + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - portainer-data:/data + +volumes: + portainer-data: From 7abe6a3c1cb564d0542c827e8fb5a6f127aa52cb Mon Sep 17 00:00:00 2001 From: Daniel Esser Date: Thu, 16 Jul 2026 09:07:23 +0200 Subject: [PATCH 06/12] Fix README heading levels and MongoDB password rotation guidance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Advanced Topics subsections were H2, same level as their H2 parent instead of nested under it. Also replaces the misleading MongoDB password example in "Changing secrets and settings" — MONGO_ROOT_PASSWORD in .env only takes effect on first initialization, not on a running installation — with the shared API key, and points to the docs for the actual rotation procedure. Co-Authored-By: Claude Sonnet 5 --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 460415f..f0f9f89 100644 --- a/README.md +++ b/README.md @@ -77,11 +77,13 @@ NodePit Runner is licensed under the [NodePit Runner: Terms and Conditions](http
Show more - ## Changing secrets and settings + ### Changing secrets and settings - `docker-compose.yml` works out of the box with built-in default secrets. To change one — e.g. rotate the MongoDB password or set a public `WEB_BASE_URL` — clone this repo, copy [`.env.example`](.env.example) to `.env`, and edit the values there. Because `.env` is read by every service that needs a given value, you only need to change it in one place. + `docker-compose.yml` works out of the box with built-in default secrets. To change one — e.g. the shared API key or a public `WEB_BASE_URL` — clone this repo, copy [`.env.example`](.env.example) to `.env`, and edit the values there. Because `.env` is read by every service that needs a given value, you only need to change it in one place. - ## Combining extensions without repeating `-f` flags + The MongoDB password is a special case: setting `MONGO_ROOT_PASSWORD` in `.env` only takes effect when the database is first initialized, not on a running installation. See [Configuration](https://docs.nodepit.com/runner/configuration) in the docs for how to rotate it safely. + + ### Combining extensions without repeating `-f` flags Instead of passing multiple `-f` flags every time, set `COMPOSE_FILE` in `.env`: @@ -91,7 +93,7 @@ NodePit Runner is licensed under the [NodePit Runner: Terms and Conditions](http and then just run `docker compose up -d`. - ## Vagrant + ### Vagrant If you use [Vagrant](https://developer.hashicorp.com/vagrant), there’s a [Vagrantfile](Vagrantfile) to run a Debian box with Docker preinstalled. Start and connect to the box as follows: From 529106ae2678e82d1bd38bf8ee77cc3716c70762 Mon Sep 17 00:00:00 2001 From: Daniel Esser Date: Thu, 16 Jul 2026 09:10:05 +0200 Subject: [PATCH 07/12] Vagrantfile: bump to debian/bookworm64 and forward extension ports debian/buster64 (Debian 10) is past its LTS window; bookworm64 is the current stable release with actively maintained Vagrant Cloud box builds (trixie64 boxes aren't reliably published yet). Also forwards 80/443/9000 so extensions/nginx and extensions/portainer are reachable from the host when running inside the box, matching the existing 8080 forward for the base stack. Co-Authored-By: Claude Sonnet 5 --- Vagrantfile | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Vagrantfile b/Vagrantfile index 3c766e2..d56eff2 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -12,7 +12,7 @@ Vagrant.configure("2") do |config| # Every Vagrant development environment requires a box. You can search for # boxes at https://vagrantcloud.com/search. - config.vm.box = "debian/buster64" + config.vm.box = "debian/bookworm64" # Disable automatic box update checking. If you disable this, then # boxes will only be checked for updates when the user runs @@ -30,6 +30,13 @@ Vagrant.configure("2") do |config| # via 127.0.0.1 to disable public access config.vm.network "forwarded_port", guest: 8080, host: 8080, host_ip: "127.0.0.1" + # Extension ports — only reachable if you're also running the + # corresponding extension inside the box: 80/443 for extensions/nginx, + # 9000 for extensions/portainer. + config.vm.network "forwarded_port", guest: 80, host: 80, host_ip: "127.0.0.1" + config.vm.network "forwarded_port", guest: 443, host: 443, host_ip: "127.0.0.1" + config.vm.network "forwarded_port", guest: 9000, host: 9000, host_ip: "127.0.0.1" + # Create a private network, which allows host-only access to the machine # using a specific IP. # config.vm.network "private_network", ip: "192.168.33.10" From 7e5b4422fee6c97c4ed8acfb8c4e6f4dbb659c78 Mon Sep 17 00:00:00 2001 From: Daniel Esser Date: Thu, 16 Jul 2026 09:20:43 +0200 Subject: [PATCH 08/12] README: add Pulls and Docs badges Adds a GitHub Container Registry pulls badge and a documentation badge alongside the existing Product/Changelog/License badges. Wraps onto a second line via
within a single

rather than two separate

tags, to avoid stacking paragraph margins between the two badge rows. Co-Authored-By: Claude Sonnet 5 --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index f0f9f89..aff4da0 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,13 @@ + + + +
+ + + From a6970bde6de78ea3af80d2f082e2a88f55add0f5 Mon Sep 17 00:00:00 2001 From: Daniel Esser Date: Thu, 16 Jul 2026 09:24:46 +0200 Subject: [PATCH 09/12] Add CI workflow to validate Compose files on every push Runs docker compose config against the base file, each extension individually, and all extensions combined, as a matrix job so failures show up per-combination in the checks list rather than one long job. This is exactly the class of bug (required-var errors, broken -f combos, interpolation typos) that surfaced only through manual testing while building these files. Also triggers on pull_request (so fork PRs get checked, since push alone only fires for pushes to this repo) and workflow_dispatch (manual runs). Sets explicit least-privilege permissions, cancels superseded runs via concurrency, and pins actions/checkout to a commit SHA rather than a mutable tag. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/validate.yml | 40 ++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/workflows/validate.yml diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..02a362e --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,40 @@ +name: Validate Compose + +on: + push: + pull_request: + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + validate: + name: Validate (${{ matrix.name }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - name: base + files: -f docker-compose.yml + - name: nginx + files: -f docker-compose.yml -f extensions/nginx/docker-compose.yml + - name: watchtower + files: -f docker-compose.yml -f extensions/watchtower/docker-compose.yml + - name: backup + files: -f docker-compose.yml -f extensions/backup/docker-compose.yml + - name: portainer + files: -f docker-compose.yml -f extensions/portainer/docker-compose.yml + - name: all extensions combined + files: -f docker-compose.yml -f extensions/nginx/docker-compose.yml -f extensions/watchtower/docker-compose.yml -f extensions/backup/docker-compose.yml -f extensions/portainer/docker-compose.yml + env: + DOMAIN: example.com + LETSENCRYPT_EMAIL: admin@example.com + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - run: docker compose ${{ matrix.files }} config --quiet From 65cb23394e6f6e582f108e9846b34b1097819881 Mon Sep 17 00:00:00 2001 From: Daniel Esser Date: Fri, 17 Jul 2026 07:54:49 +0200 Subject: [PATCH 10/12] Vagrantfile: fall back to a free host port on conflict Forwarding ports 80/443/8080/9000 unconditionally could make `vagrant up` fail outright if any of those are already taken on the host, which is common on developer machines. auto_correct: true falls back to a different free host port instead of failing when the requested one is in use. Co-Authored-By: Claude Sonnet 5 --- Vagrantfile | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Vagrantfile b/Vagrantfile index d56eff2..c730b2b 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -28,14 +28,16 @@ Vagrant.configure("2") do |config| # Create a forwarded port mapping which allows access to a specific port # within the machine from a port on the host machine and only allow access # via 127.0.0.1 to disable public access - config.vm.network "forwarded_port", guest: 8080, host: 8080, host_ip: "127.0.0.1" + # auto_correct falls back to a different free host port if the one + # requested is already taken, instead of failing `vagrant up` outright. + config.vm.network "forwarded_port", guest: 8080, host: 8080, host_ip: "127.0.0.1", auto_correct: true # Extension ports — only reachable if you're also running the # corresponding extension inside the box: 80/443 for extensions/nginx, # 9000 for extensions/portainer. - config.vm.network "forwarded_port", guest: 80, host: 80, host_ip: "127.0.0.1" - config.vm.network "forwarded_port", guest: 443, host: 443, host_ip: "127.0.0.1" - config.vm.network "forwarded_port", guest: 9000, host: 9000, host_ip: "127.0.0.1" + config.vm.network "forwarded_port", guest: 80, host: 80, host_ip: "127.0.0.1", auto_correct: true + config.vm.network "forwarded_port", guest: 443, host: 443, host_ip: "127.0.0.1", auto_correct: true + config.vm.network "forwarded_port", guest: 9000, host: 9000, host_ip: "127.0.0.1", auto_correct: true # Create a private network, which allows host-only access to the machine # using a specific IP. From a6a6a870d17d33b7e17c1497871f60257520a43e Mon Sep 17 00:00:00 2001 From: Daniel Esser Date: Fri, 17 Jul 2026 08:01:34 +0200 Subject: [PATCH 11/12] docker-compose.yml: warn that default secrets are public API_KEY and the MongoDB credentials default to fixed, repo-known values. Fine for local evaluation, but easy to accidentally deploy without overriding them in .env. Adds a one-line warning at each default so it's visible at the exact point someone might copy the value, rather than only in the top-of-file comment. Also drops a comment that only restated what the DB_URL value already shows, and tightens another to fit one line. Co-Authored-By: Claude Sonnet 5 --- docker-compose.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index a2e9680..0a82435 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,9 +32,10 @@ services: volumes: - api-data:/nodepit environment: - # Built from MONGO_ROOT_USERNAME/MONGO_ROOT_PASSWORD below. + # ⚠️ Public default — override in .env before production. DB_URL: mongodb://${MONGO_ROOT_USERNAME:-root}:${MONGO_ROOT_PASSWORD:-drnFEtX_PWKizUzKr3KT}@mongodb/nodepit-runner?authSource=admin # Shared secret with API_KEY on the executor service below. + # ⚠️ Public default — override in .env before production. API_KEY: ${API_KEY:-nYCA3oaqhYJ__GEteWye} # The URL Runner is reachable at. Change for anything beyond local use. WEB_BASE_URL: ${WEB_BASE_URL:-http://localhost:8080} @@ -76,8 +77,8 @@ services: volumes: - mongodb-data:/data/db environment: - # Only used when initializing a new database — see the docs to change - # the password later. + # Only used when initializing a new database — see docs to change it later. + # ⚠️ Public default — override in .env before production. MONGO_INITDB_ROOT_USERNAME: ${MONGO_ROOT_USERNAME:-root} MONGO_INITDB_ROOT_PASSWORD: ${MONGO_ROOT_PASSWORD:-drnFEtX_PWKizUzKr3KT} logging: *default-logging From aa68f9978c0814585437ba95917a59171956049b Mon Sep 17 00:00:00 2001 From: Daniel Esser Date: Fri, 17 Jul 2026 08:10:32 +0200 Subject: [PATCH 12/12] extensions/portainer: bind to 127.0.0.1 instead of all interfaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Portainer has the Docker socket mounted, so anyone who can log in has root-equivalent access to the host. That warrants network-level isolation by default, not just Portainer's own login screen — publish on localhost only and document the SSH tunnel needed to reach it on a remote host. Co-Authored-By: Claude Sonnet 5 --- extensions/portainer/docker-compose.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/extensions/portainer/docker-compose.yml b/extensions/portainer/docker-compose.yml index 9f10e8e..240f77e 100644 --- a/extensions/portainer/docker-compose.yml +++ b/extensions/portainer/docker-compose.yml @@ -4,6 +4,13 @@ # containers, view logs, inspect resource usage) without needing the CLI. # On first visit, it asks you to create an admin account. # +# Bound to 127.0.0.1 only — with the Docker socket mounted, anyone who can +# log in has root-equivalent access to the host, so it's not exposed +# publicly by default. On a remote host, reach it via an SSH tunnel: +# ssh -L 9000:localhost:9000 user@host +# The nginx extension doesn't proxy Portainer, only web/api — combining +# the two requires extending extensions/nginx yourself. +# # Usage: # docker compose -f docker-compose.yml -f extensions/portainer/docker-compose.yml up -d # Open http://localhost:9000 @@ -13,7 +20,7 @@ services: image: portainer/portainer-ce:2.39.5 restart: unless-stopped ports: - - 9000:9000 + - 127.0.0.1:9000:9000 volumes: - /var/run/docker.sock:/var/run/docker.sock - portainer-data:/data