8 Commits

Author SHA1 Message Date
ElevenNotes
d062133a27 fix(CI/CD)!: removal of armv7 as non-standard deployment option
Default building armv72 (32bit) images will be disabled from now on
2025-07-14 20:31:20 +02:00
ElevenNotes
d4d83345ee Merge branch 'master' of https://github.com/11notes/docker-netbird 2025-07-14 16:47:27 +02:00
ElevenNotes
2a2de68337 feat: 11notes/go as build image 2025-07-14 16:47:18 +02:00
ElevenNotes
2016f9671f feat: new postgres image and yml anchor 2025-07-14 16:47:05 +02:00
ElevenNotes
578132e39b chore: change UVP 2025-07-14 16:46:51 +02:00
github-actions[bot]
16566a986b [upgrade] 0.50.3 2025-07-13 05:15:42 +00:00
ElevenNotes
57eb9f32a8 Merge branch 'master' of https://github.com/11notes/docker-netbird 2025-07-11 07:53:07 +02:00
ElevenNotes
6279ff8a3e [upgrade] latest workflows 2025-07-11 07:39:26 +02:00
6 changed files with 123 additions and 87 deletions

70
.github/workflows/cve.yml vendored Normal file
View File

@@ -0,0 +1,70 @@
name: cve
on:
workflow_dispatch:
schedule:
- cron: "30 15 */2 * *"
jobs:
cve:
runs-on: ubuntu-latest
steps:
- name: init / checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
ref: ${{ github.ref_name }}
fetch-depth: 0
- name: init / setup environment
uses: actions/github-script@62c3794a3eb6788d9a2a72b219504732c0c9a298
with:
script: |
const { existsSync, readFileSync } = require('node:fs');
const { resolve } = require('node:path');
const { inspect } = require('node:util');
const { Buffer } = require('node:buffer');
const inputs = `${{ toJSON(github.event.inputs) }}`;
const opt = {input:{}, dot:{}};
try{
if(inputs.length > 0){
opt.input = JSON.parse(inputs);
if(opt.input?.etc){
opt.input.etc = JSON.parse(Buffer.from(opt.input.etc, 'base64').toString('ascii'));
}
}
}catch(e){
core.warning('could not parse github.event.inputs');
}
try{
const path = resolve('.json');
if(existsSync(path)){
try{
opt.dot = JSON.parse(readFileSync(path).toString());
}catch(e){
throw new Error('could not parse .json');
}
}else{
throw new Error('.json does not exist');
}
}catch(e){
core.setFailed(e);
}
core.info(inspect(opt, {showHidden:false, depth:null, colors:true}));
core.exportVariable('WORKFLOW_IMAGE', `${opt.dot.image}:${(opt.dot?.semver?.version === undefined) ? 'rolling' : opt.dot.semver.version}`);
core.exportVariable('WORKFLOW_GRYPE_SEVERITY_CUTOFF', (opt.dot?.grype?.severity || 'high'));
- name: grype / scan
id: grype
uses: anchore/scan-action@dc6246fcaf83ae86fcc6010b9824c30d7320729e
with:
image: ${{ env.WORKFLOW_IMAGE }}
fail-build: true
severity-cutoff: ${{ env.WORKFLOW_GRYPE_SEVERITY_CUTOFF }}
output-format: 'sarif'
by-cve: true
cache-db: true

View File

@@ -101,7 +101,7 @@ jobs:
const docker = {
image:{
name:opt.dot.image,
arch:(opt.dot.arch || 'linux/amd64,linux/arm64'),
arch:(opt.input?.etc?.arch || opt.dot?.arch || 'linux/amd64,linux/arm64'),
prefix:((opt.input?.etc?.semverprefix) ? `${opt.input?.etc?.semverprefix}-` : ''),
suffix:((opt.input?.etc?.semversuffix) ? `-${opt.input?.etc?.semversuffix}` : ''),
description:(opt.dot?.readme?.description || ''),
@@ -228,7 +228,7 @@ jobs:
with:
driver-opts: network=host
- name: docker / build & push & tag grype
- name: docker / build image locally
if: env.WORKFLOW_BUILD == 'true'
id: docker-build
uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d
@@ -257,7 +257,7 @@ jobs:
cache-db: true
- name: grype / fail
if: env.WORKFLOW_BUILD == 'true' && (failure() || steps.grype.outcome == 'failure')
if: env.WORKFLOW_BUILD == 'true' && (failure() || steps.grype.outcome == 'failure') && steps.docker-build.outcome == 'success'
uses: anchore/scan-action@dc6246fcaf83ae86fcc6010b9824c30d7320729e
with:
image: ${{ env.DOCKER_CACHE_GRYPE }}
@@ -267,7 +267,7 @@ jobs:
by-cve: true
cache-db: true
- name: docker / build & push
- name: docker / build image from cache and push to registries
if: env.WORKFLOW_BUILD == 'true'
uses: docker/build-push-action@67a2d409c0a876cbe6b11854e3e25193efe4e62d
with:

3
.json
View File

@@ -2,9 +2,8 @@
"image": "11notes/netbird",
"name": "netbird",
"root": "/netbird",
"arch": "linux/amd64,linux/arm64,linux/arm/v7",
"semver": {
"version": "0.50.2"
"version": "0.50.3"
},
"readme": {
"description": "Run netbird rootless and distroless from a single image.",

View File

@@ -4,7 +4,9 @@
# GLOBAL
ARG APP_UID=1000 \
APP_GID=1000 \
BUILD_ROOT="/go/netbird/management /go/netbird/relay /go/netbird/signal"
BUILD_SRC=https://github.com/netbirdio/netbird.git \
BUILD_ROOT="/go/netbird/management /go/netbird/relay /go/netbird/signal" \
GO_VERSION=1.24
# :: FOREIGN IMAGES
FROM 11notes/nginx:stable AS distroless-nginx
@@ -15,77 +17,51 @@
# ║ BUILD ║
# ╚═════════════════════════════════════════════════════╝
# :: netbird
FROM golang:1.24-alpine AS build
COPY --from=util /usr/local/bin /usr/local/bin
FROM 11notes/go:${GO_VERSION} AS build
ARG APP_VERSION \
BUILD_ROOT \
BUILD_BIN
ENV CGO_ENABLED=0
BUILD_SRC \
BUILD_ROOT
RUN set -ex; \
apk --update --no-cache add \
build-base \
upx \
git;
git clone ${BUILD_SRC} -b v${APP_VERSION}; \
sed -i 's/"development"/"v'${APP_VERSION}'"/' /go/netbird/version/version.go;
RUN set -ex; \
git clone https://github.com/netbirdio/netbird -b v${APP_VERSION};
RUN set -ex; \
mkdir -p /distroless/usr/local/bin; \
for BUILD in ${BUILD_ROOT}; do \
cd ${BUILD}; \
BIN="${BUILD}/$(echo ${BUILD} | awk -F '/' '{print $4}')"; \
go build -ldflags="-extldflags=-static" -o ${BIN} main.go; \
eleven checkStatic ${BIN}; \
eleven strip ${BIN}; \
cp ${BIN} /distroless/usr/local/bin; \
BUILD_BIN="${BUILD}/$(echo ${BUILD} | awk -F '/' '{print $4}')"; \
go mod tidy; \
eleven go build ${BUILD_BIN} main.go; \
eleven distroless ${BUILD_BIN}; \
done; \
mv /distroless/usr/local/bin/management /distroless/usr/local/bin/netbird;
# :: management
FROM golang:1.24-alpine AS management
COPY --from=util /usr/local/bin /usr/local/bin
FROM 11notes/go:${GO_VERSION} AS management
COPY ./build/go/management /go/management
ENV CGO_ENABLED=0
ARG BIN=/go/management/management
RUN set -ex; \
apk --update --no-cache add \
build-base \
upx;
ARG BUILD_BIN=/go/management/management
RUN set -ex; \
cd /go/management; \
go build -ldflags="-extldflags=-static" -o ${BIN} main.go; \
mkdir -p /distroless/usr/local/bin; \
eleven checkStatic ${BIN}; \
eleven strip ${BIN}; \
cp ${BIN} /distroless/usr/local/bin;
eleven go build ${BUILD_BIN} main.go; \
eleven distroless ${BUILD_BIN};
# :: dashboard
FROM golang:1.24-alpine AS dashboard
COPY --from=util /usr/local/bin /usr/local/bin
FROM 11notes/go:${GO_VERSION} AS dashboard
COPY ./build/go/dashboard /go/dashboard
ENV CGO_ENABLED=0
ARG BIN=/go/dashboard/dashboard
ARG BUILD_BIN=/go/dashboard/dashboard
RUN set -ex; \
apk --update --no-cache add \
build-base \
upx \
git \
nodejs \
npm;
RUN set -ex; \
cd /go/dashboard; \
go build -ldflags="-extldflags=-static" -o ${BIN} main.go; \
mkdir -p /distroless/usr/local/bin; \
eleven checkStatic ${BIN}; \
eleven strip ${BIN}; \
cp ${BIN} /distroless/usr/local/bin;
eleven go build ${BUILD_BIN} main.go; \
eleven distroless ${BUILD_BIN};
RUN set -ex; \
git clone https://github.com/netbirdio/dashboard /dashboard;
@@ -100,7 +76,7 @@
# :: file system
FROM alpine AS file-system
COPY --from=util /usr/local/bin /usr/local/bin
COPY --from=util / /
ARG APP_ROOT
USER root

View File

@@ -1,4 +1,9 @@
name: "netbird"
x-image-netbird: &image
image: "11notes/netbird:0.50.2"
read_only: true
services:
db:
image: "11notes/postgres:16"
@@ -6,22 +11,22 @@ services:
environment:
TZ: "Europe/Zurich"
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
# make a full and compressed database backup each day at 03:00
POSTGRES_BACKUP_SCHEDULE: "0 3 * * *"
volumes:
- "db.etc:/postgres/etc"
- "db.var:/postgres/var"
- "db.backup:/postgres/backup"
# used for optional cron container to create automatic backups
- "db.cmd:/run/cmd"
tmpfs:
- "/run/postgresql:uid=1000,gid=1000"
# needed for read-only
- "/postgres/run:uid=1000,gid=1000"
- "/postgres/log:uid=1000,gid=1000"
networks:
backend:
restart: "always"
dashboard:
image: "11notes/netbird:0.48.0"
read_only: true
<<: *image
environment:
NETBIRD_MGMT_API_ENDPOINT: "https://${NETBIRD_FQDN}"
NETBIRD_MGMT_GRPC_API_ENDPOINT: "https://${NETBIRD_FQDN}"
@@ -54,8 +59,7 @@ services:
db:
condition: "service_healthy"
restart: true
image: "11notes/netbird:0.48.0"
read_only: true
<<: *image
env_file: '.env'
environment:
TZ: "Europe/Zurich"
@@ -82,7 +86,7 @@ services:
restart: "always"
signal:
image: "11notes/netbird:0.48.0"
<<: *image
environment:
TZ: "Europe/Zurich"
entrypoint: ["/usr/local/bin/signal"]
@@ -100,7 +104,7 @@ services:
restart: "always"
relay:
image: "11notes/netbird:0.48.0"
<<: *image
environment:
TZ: "Europe/Zurich"
NB_LISTEN_ADDRESS: ":33080"
@@ -113,22 +117,6 @@ services:
- "33080:33080/tcp"
restart: "always"
# optional images
cron:
depends_on:
db:
condition: "service_healthy"
restart: true
image: "11notes/cron:4.6"
environment:
TZ: "Europe/Zurich"
# create daily full backup at 3 o'clock
CRONTAB: |-
0 3 * * * cmd-socket '{"bin":"backup"}' > /proc/1/fd/1
volumes:
- "db.cmd:/run/cmd"
restart: "always"
volumes:
management.etc:
management.var:
@@ -137,7 +125,6 @@ volumes:
db.etc:
db.var:
db.backup:
db.cmd:
networks:
frontend:

View File

@@ -2,15 +2,19 @@ ${{ content_synopsis }} This image will run netbird from a single image (not mul
The init binary **management** will replace all variables in the format ```${VARIABLE}``` with all environment variables present in the service.
${{ github:> [!IMPORTANT] }}
${{ github:> }}* This image runs as 1000:1000 by default, most other images run everything as root
${{ github:> }}* This image has no shell since it is distroless, most other images run on a distro like Debian or Alpine with full shell access (security)
${{ github:> }}* This image does not ship with any critical or high rated CVE and is automatically maintained via CI/CD, most other images mostly have no CVE scanning or code quality tools in place
${{ github:> }}* This image is created via a secure, pinned CI/CD process and immune to upstream attacks, most other images have upstream dependencies that can be exploited
${{ github:> }}* This image works as read-only, most other images need to write files to the image filesystem
${{ github:> }}* This image is a lot smaller than most other images
${{ content_uvp }} Good question! Because ...
If you value security, simplicity and the ability to interact with the maintainer and developer of an image. Using my images is a great start in that direction.
${{ github:> [!IMPORTANT] }}
${{ github:> }}* ... this image runs [rootless](https://github.com/11notes/RTFM/blob/main/linux/container/image/rootless.md) as 1000:1000
${{ github:> }}* ... this image has no shell since it is [distroless](https://github.com/11notes/RTFM/blob/main/linux/container/image/distroless.md)
${{ github:> }}* ... this image is auto updated to the latest version via CI/CD
${{ github:> }}* ... this image has a health check
${{ github:> }}* ... this image runs read-only
${{ github:> }}* ... this image is automatically scanned for CVEs before and after publishing
${{ github:> }}* ... this image is created via a secure and pinned CI/CD process
${{ github:> }}* ... this image is very small
If you value security, simplicity and optimizations to the extreme, then this image might be for you.
# COMPARISON 🏁
Below you find a comparison between this image and the most used or original one.
@@ -24,7 +28,7 @@ Below you find a comparison between this image and the most used or original one
${{ title_volumes }}
* **${{ json_root }}/etc** - Directory of your management.json config
* **${{ json_root }}/var** - Directory of dynamic data from differnet init systems (relay, signal, management)
* **${{ json_root }}/var** - Directory of dynamic data from different init systems (relay, signal, management)
# EXAMPLE ENV FILE 📑
```ini
@@ -63,4 +67,4 @@ ${{ content_tips }}
${{ title_caution }}
${{ github:> [!CAUTION] }}
${{ github:> }}* Because this image is distroless, it only works with PostgreSQL, not SQLite. The GeoLocation middleware is also disabled because of this!
${{ github:> }}* Because this image is distroless, it only works with PostgreSQL, **not SQLite**. The GeoLocation middleware is also disabled because of this!