Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 56 additions & 6 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ name: Build and Publish NUT Server Docker Images
on:
push:
branches: [ "main", "master" ]
tags: [ 'v*.*.*' ]
pull_request:
branches: [ "main", "master" ]
schedule:
# Run automatically on the 1st of every month at 03:00 UTC
# Monthly rebuild (1st, 03:00 UTC): refreshes Debian base security patches
# and re-detects the packaged NUT version. The build also commits a small
# NUT_VERSION stamp, which doubles as the keepalive that stops GitHub from
# auto-disabling this schedule after 60 days of repo inactivity.
- cron: '0 3 1 * *'
workflow_dispatch:

Expand All @@ -19,7 +21,7 @@ jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
contents: write
packages: write
id-token: write

Expand All @@ -41,22 +43,44 @@ jobs:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}

# The image installs nut-server from Debian bookworm, so its version is
# whatever bookworm currently ships. Resolve the candidate here (package
# versions are identical across arches) and use it to tag and label the
# image, so users can pin/inspect by NUT version instead of build date.
- name: Resolve packaged NUT version
id: nut
run: |
full=$(docker run --rm debian:bookworm-slim sh -c \
'apt-get update -qq && apt-cache policy nut-server 2>/dev/null | awk "/Candidate:/{print \$2}"')
if [ -z "$full" ]; then echo "Could not resolve nut-server version" >&2; exit 1; fi
echo "full=$full" >> "$GITHUB_OUTPUT"
echo "upstream=${full%%-*}" >> "$GITHUB_OUTPUT"
echo "Resolved NUT: full=$full upstream=${full%%-*}"

- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest,enable={{is_default_branch}}
# NUT version tags: full Debian version (e.g. 2.8.0-7) plus the
# upstream pointer (e.g. 2.8.0) that moves to the latest Debian
# revision. This is the primary way to pin a server version.
type=raw,value=${{ steps.nut.outputs.full }},enable={{is_default_branch}}
type=raw,value=${{ steps.nut.outputs.upstream }},enable={{is_default_branch}}
# Date tag: immutable stamp of a specific monthly build.
type=raw,value={{date 'YYYY.MM'}},enable={{is_default_branch}}
# Legacy aliases kept in sync with `latest` so users who pinned
# these 2021-era tags keep receiving the current multi-arch image.
type=raw,value=amd64-latest,enable={{is_default_branch}}
type=raw,value=arm64v8-latest,enable={{is_default_branch}}
type=raw,value=arm32v7-latest,enable={{is_default_branch}}
labels: |
org.opencontainers.image.version=${{ steps.nut.outputs.full }}
org.networkupstools.version=${{ steps.nut.outputs.full }}
org.networkupstools.upstream=${{ steps.nut.outputs.upstream }}

- name: Build and push Docker image
id: build-and-push
Expand All @@ -77,4 +101,30 @@ jobs:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
repository: ${{ env.IMAGE_NAME }}
readme-filepath: ./README.md
readme-filepath: ./README.md

# Record the built version in-repo and commit it. This gives a
# human-readable version log and, because it runs on the monthly
# schedule, keeps the repo active so GitHub does not auto-disable the
# cron after 60 days of inactivity. [skip ci] avoids a redundant run
# (a GITHUB_TOKEN push does not trigger workflows anyway).
- name: Record built NUT version (keepalive)
if: github.ref == 'refs/heads/master' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch')
run: |
cat > NUT_VERSION <<EOF
# Auto-generated by docker-publish.yml — do not edit by hand.
# NUT server version baked into the current nardo86/nut-server images.
nut-server=${{ steps.nut.outputs.full }}
upstream=${{ steps.nut.outputs.upstream }}
debian=bookworm
built=$(date -u +%Y-%m-%d)
EOF
if git diff --quiet -- NUT_VERSION; then
echo "NUT_VERSION unchanged, nothing to commit."
else
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add NUT_VERSION
git commit -m "chore(build): NUT ${{ steps.nut.outputs.full }} ($(date -u +%Y-%m-%d)) [skip ci]"
git push origin HEAD:${{ github.ref_name }}
fi
6 changes: 6 additions & 0 deletions NUT_VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Auto-generated by docker-publish.yml — do not edit by hand.
# NUT server version baked into the current nardo86/nut-server images.
nut-server=2.8.0-7
upstream=2.8.0
debian=bookworm
built=2026-07-01
35 changes: 33 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,36 @@ docker run -d --name nut-server -p 3493:3493 \
nardo86/nut-server:latest
```

## Tags & versions

The NUT server comes from Debian's `nut-server` package, so its version is
whatever Debian stable currently ships (bookworm → upstream **2.8.0**). Images
are tagged along two independent axes — *which NUT version* and *which build*:

| Tag | Example | Meaning |
|------------------|------------|-------------------------------------------------------------------------|
| `X.Y.Z-R` | `2.8.0-7` | Exact Debian package version. Pin this for a specific server version. |
| `X.Y.Z` | `2.8.0` | Upstream version; moves to the newest Debian revision of that version. |
| `YYYY.MM` | `2026.07` | Immutable stamp of one monthly build (base-OS patch level). |
| `latest` | | Newest build. |
| `*-latest` | `amd64-latest` | Legacy 2021-era aliases, kept in sync with `latest`. |

The image is rebuilt monthly to pick up Debian security patches; the NUT
version itself only changes when Debian does (at the next stable release). The
packaged version is recorded in [`NUT_VERSION`](NUT_VERSION) and in the image's
OCI labels, so you can check any tag without guessing from the tag name:

```bash
# From the image metadata (no need to run it):
docker inspect --format '{{ index .Config.Labels "org.networkupstools.version" }}' \
nardo86/nut-server:2.8.0
# Straight from the server binary:
docker run --rm --entrypoint upsd nardo86/nut-server:2.8.0 -V
```

For the record: tags `2025.06`–`2025.10` were the previous bullseye-based line
(NUT `2.7.4-13`); `2026.05` onward are bookworm (NUT `2.8.0-7`).

## Configuration

| Variable | Description | Default |
Expand Down Expand Up @@ -101,8 +131,9 @@ Common alternatives to `usbhid-ups`: `blazer_usb`, `nutdrv_qx`.

## Notes

- **NUT version** tracks whatever Debian stable ships (currently bookworm),
not the upstream latest.
- **NUT version** tracks whatever Debian stable ships (currently bookworm,
NUT 2.8.0), not the upstream latest — see [Tags & versions](#tags--versions)
for how to pin or inspect a specific version.
- **Built with help from Claude (Anthropic).** Review the configuration before
production use. No warranty.
- Image: <https://hub.docker.com/r/nardo86/nut-server>
Expand Down
Loading