Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c452c17583 | ||
|
|
d062133a27 | ||
|
|
d4d83345ee | ||
|
|
2a2de68337 | ||
|
|
2016f9671f | ||
|
|
578132e39b | ||
|
|
16566a986b | ||
|
|
57eb9f32a8 | ||
|
|
6279ff8a3e | ||
|
|
8e12d4bd93 |
70
.github/workflows/cve.yml
vendored
Normal file
70
.github/workflows/cve.yml
vendored
Normal 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
|
||||
10
.github/workflows/docker.yml
vendored
10
.github/workflows/docker.yml
vendored
@@ -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:
|
||||
@@ -419,7 +419,7 @@ jobs:
|
||||
if [ -f LICENSE ]; then
|
||||
git add LICENSE
|
||||
fi
|
||||
git commit -m "github-actions[bot]: update README.md"
|
||||
git commit -m "docs: auto update README.md"
|
||||
git push origin HEAD:master
|
||||
|
||||
|
||||
|
||||
3
.json
3
.json
@@ -2,9 +2,8 @@
|
||||
"image": "11notes/netbird",
|
||||
"name": "netbird",
|
||||
"root": "/netbird",
|
||||
"arch": "linux/amd64,linux/arm64,linux/arm/v7",
|
||||
"semver": {
|
||||
"version": "0.50.1"
|
||||
"version": "0.50.3"
|
||||
},
|
||||
"readme": {
|
||||
"description": "Run netbird rootless and distroless from a single image.",
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
39
compose.yaml
39
compose.yaml
@@ -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:
|
||||
|
||||
24
project.md
24
project.md
@@ -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!
|
||||
Reference in New Issue
Block a user