Compare commits

...

14 Commits

Author SHA1 Message Date
Diego Noguês
76fb153d76 fix: small conflicts 2026-02-12 18:41:57 +01:00
Diego Noguês
eee4d75932 chore: switching env var for NB_PROXY_DOMAIN 2026-02-12 18:35:30 +01:00
Diego Noguês
62b8875f67 chore: build 2026-02-12 17:42:03 +01:00
Diego Noguês
47a5478964 chore: remove unnecessary comment 2026-02-12 17:18:23 +01:00
Diego Noguês
9922d6f953 feat: switch to labels for traefik instead of static conf files 2026-02-12 17:18:23 +01:00
Diego Noguês
f9bab22f61 fix: remove change to peers group all 2026-02-12 17:18:23 +01:00
Diego Noguês
3d8fdb7a89 feat: adding IPAM settings to docker compose and setting static ip to traefik 2026-02-12 17:18:23 +01:00
Diego Noguês
fb10153ab8 feat: adding traefik and proxy component to getting-started 2026-02-12 17:17:30 +01:00
Diego Noguês
b00babb8b1 feat: switch to labels for traefik instead of static conf files 2026-02-12 16:50:43 +01:00
Diego Noguês
3bc8cbb13f fix: remove change to peers group all 2026-02-12 14:06:23 +01:00
Diego Noguês
bf7bdf6c4f feat: adding IPAM settings to docker compose and setting static ip to traefik 2026-02-12 14:02:21 +01:00
Diego Noguês
0a895ffc22 Merge branch 'dn-reverse-proxy' of github.com:netbirdio/netbird into dn-reverse-proxy 2026-02-12 12:00:17 +01:00
Diego Noguês
b81837a364 feat: adding traefik and proxy component to getting-started 2026-02-12 11:16:37 +01:00
Diego Noguês
b782ac6f56 feat: adding traefik and proxy component to getting-started 2026-02-11 15:34:18 +01:00
4 changed files with 503 additions and 23 deletions

View File

@@ -91,16 +91,17 @@ read_reverse_proxy_type() {
echo " [3] Nginx Proxy Manager (generates config + instructions)" > /dev/stderr echo " [3] Nginx Proxy Manager (generates config + instructions)" > /dev/stderr
echo " [4] External Caddy (generates Caddyfile snippet)" > /dev/stderr echo " [4] External Caddy (generates Caddyfile snippet)" > /dev/stderr
echo " [5] Other/Manual (displays setup documentation)" > /dev/stderr echo " [5] Other/Manual (displays setup documentation)" > /dev/stderr
echo " [6] Traefik TCP Proxy (single port 443 + STUN)" > /dev/stderr
echo "" > /dev/stderr echo "" > /dev/stderr
echo -n "Enter choice [0-5] (default: 0): " > /dev/stderr echo -n "Enter choice [0-6] (default: 0): " > /dev/stderr
read -r CHOICE < /dev/tty read -r CHOICE < /dev/tty
if [[ -z "$CHOICE" ]]; then if [[ -z "$CHOICE" ]]; then
CHOICE="0" CHOICE="0"
fi fi
if [[ ! "$CHOICE" =~ ^[0-5]$ ]]; then if [[ ! "$CHOICE" =~ ^[0-6]$ ]]; then
echo "Invalid choice. Please enter a number between 0 and 5." > /dev/stderr echo "Invalid choice. Please enter a number between 0 and 6." > /dev/stderr
read_reverse_proxy_type read_reverse_proxy_type
return return
fi fi
@@ -140,6 +141,35 @@ read_traefik_certresolver() {
return 0 return 0
} }
read_traefik_tcp_acme_email() {
echo "" > /dev/stderr
echo "Enter your email for Let's Encrypt certificate notifications." > /dev/stderr
echo -n "Email address: " > /dev/stderr
read -r EMAIL < /dev/tty
if [[ -z "$EMAIL" ]]; then
echo "Email is required for Let's Encrypt." > /dev/stderr
read_traefik_tcp_acme_email
return
fi
echo "$EMAIL"
return 0
}
read_enable_proxy() {
echo "" > /dev/stderr
echo "Do you want to enable the NetBird Proxy service?" > /dev/stderr
echo "The proxy exposes internal NetBird network resources to the internet." > /dev/stderr
echo -n "Enable proxy? [y/N]: " > /dev/stderr
read -r CHOICE < /dev/tty
if [[ "$CHOICE" =~ ^[Yy]$ ]]; then
echo "true"
else
echo "false"
fi
return 0
}
read_port_binding_preference() { read_port_binding_preference() {
echo "" > /dev/stderr echo "" > /dev/stderr
echo "Should container ports be bound to localhost only (127.0.0.1)?" > /dev/stderr echo "Should container ports be bound to localhost only (127.0.0.1)?" > /dev/stderr
@@ -206,6 +236,30 @@ wait_management() {
return 0 return 0
} }
wait_management_traefik() {
set +e
echo -n "Waiting for Management server to become ready"
counter=1
while true; do
# Check the embedded IdP endpoint through Traefik
if curl -sk -f -o /dev/null "$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth2/.well-known/openid-configuration" 2>/dev/null; then
break
fi
if [[ $counter -eq 60 ]]; then
echo ""
echo "Taking too long. Checking logs..."
$DOCKER_COMPOSE_COMMAND logs --tail=20 traefik
$DOCKER_COMPOSE_COMMAND logs --tail=20 management
fi
echo -n " ."
sleep 2
counter=$((counter + 1))
done
echo " done"
set -e
return 0
}
wait_management_direct() { wait_management_direct() {
set +e set +e
local upstream_host=$(get_upstream_host) local upstream_host=$(get_upstream_host)
@@ -246,10 +300,12 @@ initialize_default_values() {
# Docker images # Docker images
CADDY_IMAGE="caddy" CADDY_IMAGE="caddy"
DASHBOARD_IMAGE="netbirdio/dashboard:latest" #DASHBOARD_IMAGE="netbirdio/dashboard:latest"
DASHBOARD_IMAGE="netbirdio/dashboard:pr-552"
SIGNAL_IMAGE="netbirdio/signal:latest" SIGNAL_IMAGE="netbirdio/signal:latest"
RELAY_IMAGE="netbirdio/relay:latest" RELAY_IMAGE="netbirdio/relay:latest"
MANAGEMENT_IMAGE="netbirdio/management:latest" MANAGEMENT_IMAGE="netbirdio/management:latest"
PROXY_IMAGE=""
# Reverse proxy configuration # Reverse proxy configuration
REVERSE_PROXY_TYPE="0" REVERSE_PROXY_TYPE="0"
@@ -263,6 +319,14 @@ initialize_default_values() {
RELAY_HOST_PORT="8084" RELAY_HOST_PORT="8084"
BIND_LOCALHOST_ONLY="true" BIND_LOCALHOST_ONLY="true"
EXTERNAL_PROXY_NETWORK="" EXTERNAL_PROXY_NETWORK=""
# Traefik TCP proxy configuration
TRAEFIK_IMAGE="traefik:v3.6"
TRAEFIK_TCP_ACME_EMAIL=""
# NetBird Proxy configuration
ENABLE_PROXY="false"
PROXY_TOKEN=""
return 0 return 0
} }
@@ -293,8 +357,17 @@ configure_reverse_proxy() {
TRAEFIK_CERTRESOLVER=$(read_traefik_certresolver) TRAEFIK_CERTRESOLVER=$(read_traefik_certresolver)
fi fi
# Handle Traefik TCP proxy prompts
if [[ "$REVERSE_PROXY_TYPE" == "6" ]]; then
TRAEFIK_TCP_ACME_EMAIL=$(read_traefik_tcp_acme_email)
# Prompt for NetBird Proxy configuration
ENABLE_PROXY=$(read_enable_proxy)
# Note: PROXY_TOKEN will be auto-generated after Management starts
fi
# Handle port binding for external proxy options (2-5) # Handle port binding for external proxy options (2-5)
if [[ "$REVERSE_PROXY_TYPE" -ge 2 ]]; then if [[ "$REVERSE_PROXY_TYPE" -ge 2 && "$REVERSE_PROXY_TYPE" -le 5 ]]; then
BIND_LOCALHOST_ONLY=$(read_port_binding_preference) BIND_LOCALHOST_ONLY=$(read_port_binding_preference)
fi fi
@@ -313,7 +386,7 @@ check_existing_installation() {
echo "Generated files already exist, if you want to reinitialize the environment, please remove them first." echo "Generated files already exist, if you want to reinitialize the environment, please remove them first."
echo "You can use the following commands:" echo "You can use the following commands:"
echo " $DOCKER_COMPOSE_COMMAND down --volumes # to remove all containers and volumes" echo " $DOCKER_COMPOSE_COMMAND down --volumes # to remove all containers and volumes"
echo " rm -f docker-compose.yml Caddyfile dashboard.env management.json relay.env nginx-netbird.conf caddyfile-netbird.txt npm-advanced-config.txt" echo " rm -f docker-compose.yml Caddyfile dashboard.env management.json relay.env nginx-netbird.conf caddyfile-netbird.txt npm-advanced-config.txt proxy.env"
echo "Be aware that this will remove all data from the database, and you will have to reconfigure the dashboard." echo "Be aware that this will remove all data from the database, and you will have to reconfigure the dashboard."
exit 1 exit 1
fi fi
@@ -347,6 +420,15 @@ generate_configuration_files() {
5) 5)
render_docker_compose_exposed_ports > docker-compose.yml render_docker_compose_exposed_ports > docker-compose.yml
;; ;;
6)
render_docker_compose_traefik_tcp > docker-compose.yml
if [[ "$ENABLE_PROXY" == "true" ]]; then
# Create placeholder proxy.env so docker-compose can validate
# This will be overwritten with the actual token after Management starts
echo "# Placeholder - will be updated with token after Management starts" > proxy.env
echo "NB_PROXY_TOKEN=placeholder" >> proxy.env
fi
;;
*) *)
echo "Invalid reverse proxy type: $REVERSE_PROXY_TYPE" > /dev/stderr echo "Invalid reverse proxy type: $REVERSE_PROXY_TYPE" > /dev/stderr
exit 1 exit 1
@@ -402,6 +484,50 @@ start_services_and_show_instructions() {
echo "" echo ""
echo "NetBird containers are running. Configure NPM as shown above, then access:" echo "NetBird containers are running. Configure NPM as shown above, then access:"
echo " $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN" echo " $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN"
elif [[ "$REVERSE_PROXY_TYPE" == "6" ]]; then
# Traefik TCP Proxy - two-phase startup if proxy is enabled
echo -e "$MSG_STARTING_SERVICES"
if [[ "$ENABLE_PROXY" == "true" ]]; then
# Phase 1: Start core services (without proxy)
echo "Starting core services..."
$DOCKER_COMPOSE_COMMAND up -d traefik dashboard signal relay management
sleep 3
wait_management_traefik
# Phase 2: Create proxy token and start proxy
echo ""
echo "Creating proxy access token..."
# Use docker exec with bash to run the token command directly
# (bypassing the entrypoint which adds 'management' as first arg)
PROXY_TOKEN=$($DOCKER_COMPOSE_COMMAND exec -T management \
bash -c '/go/bin/netbird-mgmt token create --name "default-proxy" --config /etc/netbird/management.json' 2>/dev/null | grep "^Token:" | awk '{print $2}')
if [[ -z "$PROXY_TOKEN" ]]; then
echo "ERROR: Failed to create proxy token. Check management logs." > /dev/stderr
$DOCKER_COMPOSE_COMMAND logs --tail=20 management
exit 1
fi
echo "Proxy token created successfully."
# Generate proxy.env with the token
render_proxy_env > proxy.env
# Start proxy service
echo "Starting proxy service..."
$DOCKER_COMPOSE_COMMAND up -d proxy
else
# No proxy - start all services at once
$DOCKER_COMPOSE_COMMAND up -d
sleep 3
wait_management_traefik
fi
echo -e "$MSG_DONE"
print_post_setup_instructions
else else
# External proxies (nginx, external Caddy, other) - need manual config first # External proxies (nginx, external Caddy, other) - need manual config first
print_post_setup_instructions print_post_setup_instructions
@@ -547,6 +673,29 @@ EOF
return 0 return 0
} }
render_proxy_env() {
cat <<EOF
# NetBird Proxy Configuration
NB_PROXY_DEBUG_LOGS=false
# Use internal Docker network to connect to management (avoids hairpin NAT issues)
NB_PROXY_MANAGEMENT_ADDRESS=http://management:80
# Allow insecure gRPC connection to management (required for internal Docker network)
NB_PROXY_ALLOW_INSECURE=true
# Public URL where this proxy is reachable (used for cluster registration)
NB_PROXY_DOMAIN=$NETBIRD_DOMAIN
NB_PROXY_ADDRESS=:8443
NB_PROXY_TOKEN=$PROXY_TOKEN
NB_PROXY_CERTIFICATE_DIRECTORY=/certs
NB_PROXY_ACME_CERTIFICATES=true
NB_PROXY_ACME_CHALLENGE_TYPE=tls-alpn-01
NB_PROXY_OIDC_CLIENT_ID=netbird-proxy
NB_PROXY_OIDC_ENDPOINT=$NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN/oauth2
NB_PROXY_OIDC_SCOPES=openid,profile,email
NB_PROXY_FORWARDED_PROTO=https
EOF
return 0
}
render_docker_compose() { render_docker_compose() {
cat <<EOF cat <<EOF
services: services:
@@ -736,7 +885,18 @@ $(if [[ -n "$tls_labels" ]]; then echo " - traefik.http.routers.netbird-rel
# Management (includes embedded IdP) # Management (includes embedded IdP)
management: management:
$(if [[ "$ENABLE_PROXY" == "true" ]]; then
cat <<MGMT_BUILD
build:
context: ..
dockerfile: management/Dockerfile.multistage
pull_policy: build
MGMT_BUILD
else
cat <<MGMT_IMAGE
image: $MANAGEMENT_IMAGE image: $MANAGEMENT_IMAGE
MGMT_IMAGE
fi)
container_name: netbird-management container_name: netbird-management
restart: unless-stopped restart: unless-stopped
networks: [$network_name] networks: [$network_name]
@@ -1115,6 +1275,258 @@ EOF
return 0 return 0
} }
render_docker_compose_traefik_tcp() {
# Generate proxy service section if enabled
local proxy_service=""
local proxy_volumes=""
local proxy_tcp_labels=""
if [[ "$ENABLE_PROXY" == "true" ]]; then
proxy_service="
# NetBird Proxy - exposes internal resources to the internet
proxy:
build:
context: ../
dockerfile: proxy/Dockerfile
# Always rebuild to pick up code changes during testing
pull_policy: build
#image: $PROXY_IMAGE
container_name: netbird-proxy
# Hairpin NAT fix: route domain back to traefik's static IP within Docker
extra_hosts:
- \"$NETBIRD_DOMAIN:172.30.0.10\"
restart: unless-stopped
networks: [netbird]
depends_on:
- signal
env_file:
- ./proxy.env
volumes:
- netbird_proxy_certs:/certs
labels:
# TCP passthrough for any unmatched domain (proxy handles its own TLS)
- traefik.enable=true
- traefik.tcp.routers.proxy-passthrough.entrypoints=websecure
- traefik.tcp.routers.proxy-passthrough.rule=HostSNI(\`*\`)
- traefik.tcp.routers.proxy-passthrough.tls.passthrough=true
- traefik.tcp.routers.proxy-passthrough.service=proxy-tls
- traefik.tcp.routers.proxy-passthrough.priority=1
- traefik.tcp.services.proxy-tls.loadbalancer.server.port=8443
logging:
driver: \"json-file\"
options:
max-size: \"500m\"
max-file: \"2\"
"
proxy_volumes="
netbird_proxy_certs:"
fi
cat <<EOF
services:
# Traefik - single port 443 entry point with TLS termination
traefik:
image: $TRAEFIK_IMAGE
container_name: netbird-traefik
restart: unless-stopped
networks:
netbird:
ipv4_address: 172.30.0.10
ports:
- '443:443'
volumes:
- netbird_traefik_data:/data
- /var/run/docker.sock:/var/run/docker.sock:ro
command:
# Logging
- --log.level=INFO
- --accesslog=true
# Docker provider
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --providers.docker.network=netbird
# Entrypoints
- --entrypoints.websecure.address=:443
- --entrypoints.websecure.allowACMEByPass=true
# Disable timeouts for long-lived gRPC streams
- --entrypoints.websecure.transport.respondingtimeouts.readtimeout=0s
- --entrypoints.websecure.transport.respondingtimeouts.writetimeout=0s
- --entrypoints.websecure.transport.respondingtimeouts.idletimeout=0s
# Let's Encrypt ACME
- --certificatesresolvers.letsencrypt.acme.email=$TRAEFIK_TCP_ACME_EMAIL
- --certificatesresolvers.letsencrypt.acme.storage=/data/acme.json
- --certificatesresolvers.letsencrypt.acme.tlschallenge=true
# gRPC transport settings (disable response timeout for long-lived streams)
- --serverstransport.forwardingtimeouts.responseheadertimeout=0s
- --serverstransport.forwardingtimeouts.idleconntimeout=0s
logging:
driver: "json-file"
options:
max-size: "500m"
max-file: "2"
# UI dashboard
dashboard:
image: $DASHBOARD_IMAGE
container_name: netbird-dashboard
restart: unless-stopped
networks: [netbird]
env_file:
- ./dashboard.env
labels:
- traefik.enable=true
- traefik.http.routers.netbird-dashboard.entrypoints=websecure
- traefik.http.routers.netbird-dashboard.rule=Host(\`$NETBIRD_DOMAIN\`)
- traefik.http.routers.netbird-dashboard.tls=true
- traefik.http.routers.netbird-dashboard.tls.certresolver=letsencrypt
- traefik.http.routers.netbird-dashboard.service=dashboard
- traefik.http.routers.netbird-dashboard.priority=1
- traefik.http.services.dashboard.loadbalancer.server.port=80
logging:
driver: "json-file"
options:
max-size: "500m"
max-file: "2"
# Signal
signal:
image: $SIGNAL_IMAGE
container_name: netbird-signal
restart: unless-stopped
networks: [netbird]
labels:
- traefik.enable=true
# Signal WebSocket
- traefik.http.routers.netbird-signal-ws.entrypoints=websecure
- traefik.http.routers.netbird-signal-ws.rule=Host(\`$NETBIRD_DOMAIN\`) && PathPrefix(\`/ws-proxy/signal\`)
- traefik.http.routers.netbird-signal-ws.tls=true
- traefik.http.routers.netbird-signal-ws.tls.certresolver=letsencrypt
- traefik.http.routers.netbird-signal-ws.service=signal-ws
- traefik.http.routers.netbird-signal-ws.priority=100
- traefik.http.services.signal-ws.loadbalancer.server.port=80
# Signal gRPC
- traefik.http.routers.netbird-signal-grpc.entrypoints=websecure
- traefik.http.routers.netbird-signal-grpc.rule=Host(\`$NETBIRD_DOMAIN\`) && PathPrefix(\`/signalexchange.SignalExchange/\`)
- traefik.http.routers.netbird-signal-grpc.tls=true
- traefik.http.routers.netbird-signal-grpc.tls.certresolver=letsencrypt
- traefik.http.routers.netbird-signal-grpc.service=signal-grpc
- traefik.http.routers.netbird-signal-grpc.priority=100
- traefik.http.services.signal-grpc.loadbalancer.server.port=10000
- traefik.http.services.signal-grpc.loadbalancer.server.scheme=h2c
logging:
driver: "json-file"
options:
max-size: "500m"
max-file: "2"
# Relay (includes embedded STUN server)
relay:
image: $RELAY_IMAGE
container_name: netbird-relay
restart: unless-stopped
networks: [netbird]
ports:
- '$NETBIRD_STUN_PORT:$NETBIRD_STUN_PORT/udp'
env_file:
- ./relay.env
labels:
- traefik.enable=true
- traefik.http.routers.netbird-relay.entrypoints=websecure
- traefik.http.routers.netbird-relay.rule=Host(\`$NETBIRD_DOMAIN\`) && PathPrefix(\`/relay\`)
- traefik.http.routers.netbird-relay.tls=true
- traefik.http.routers.netbird-relay.tls.certresolver=letsencrypt
- traefik.http.routers.netbird-relay.service=relay
- traefik.http.routers.netbird-relay.priority=100
- traefik.http.services.relay.loadbalancer.server.port=80
logging:
driver: "json-file"
options:
max-size: "500m"
max-file: "2"
# Management (includes embedded IdP)
management:
$(if [[ "$ENABLE_PROXY" == "true" ]]; then
cat <<MGMT_BUILD
build:
context: ..
dockerfile: management/Dockerfile.multistage
pull_policy: build
MGMT_BUILD
else
cat <<MGMT_IMAGE
image: $MANAGEMENT_IMAGE
MGMT_IMAGE
fi)
container_name: netbird-management
restart: unless-stopped
networks: [netbird]
volumes:
- netbird_management:/var/lib/netbird
- ./management.json:/etc/netbird/management.json
command: [
"--port", "80",
"--log-file", "console",
"--log-level", "info",
"--disable-anonymous-metrics=false",
"--single-account-mode-domain=netbird.selfhosted",
"--dns-domain=netbird.selfhosted",
"--idp-sign-key-refresh-enabled",
]
labels:
- traefik.enable=true
# Management API
- traefik.http.routers.netbird-api.entrypoints=websecure
- traefik.http.routers.netbird-api.rule=Host(\`$NETBIRD_DOMAIN\`) && PathPrefix(\`/api\`)
- traefik.http.routers.netbird-api.tls=true
- traefik.http.routers.netbird-api.tls.certresolver=letsencrypt
- traefik.http.routers.netbird-api.service=management
- traefik.http.routers.netbird-api.priority=100
# Management WebSocket
- traefik.http.routers.netbird-mgmt-ws.entrypoints=websecure
- traefik.http.routers.netbird-mgmt-ws.rule=Host(\`$NETBIRD_DOMAIN\`) && PathPrefix(\`/ws-proxy/management\`)
- traefik.http.routers.netbird-mgmt-ws.tls=true
- traefik.http.routers.netbird-mgmt-ws.tls.certresolver=letsencrypt
- traefik.http.routers.netbird-mgmt-ws.service=management
- traefik.http.routers.netbird-mgmt-ws.priority=100
# Management gRPC
- traefik.http.routers.netbird-mgmt-grpc.entrypoints=websecure
- traefik.http.routers.netbird-mgmt-grpc.rule=Host(\`$NETBIRD_DOMAIN\`) && PathPrefix(\`/management.ManagementService/\`)
- traefik.http.routers.netbird-mgmt-grpc.tls=true
- traefik.http.routers.netbird-mgmt-grpc.tls.certresolver=letsencrypt
- traefik.http.routers.netbird-mgmt-grpc.service=management-grpc
- traefik.http.routers.netbird-mgmt-grpc.priority=100
# OAuth2 (embedded IdP)
- traefik.http.routers.netbird-oauth2.entrypoints=websecure
- traefik.http.routers.netbird-oauth2.rule=Host(\`$NETBIRD_DOMAIN\`) && PathPrefix(\`/oauth2\`)
- traefik.http.routers.netbird-oauth2.tls=true
- traefik.http.routers.netbird-oauth2.tls.certresolver=letsencrypt
- traefik.http.routers.netbird-oauth2.service=management
- traefik.http.routers.netbird-oauth2.priority=100
# Services
- traefik.http.services.management.loadbalancer.server.port=80
- traefik.http.services.management-grpc.loadbalancer.server.port=80
- traefik.http.services.management-grpc.loadbalancer.server.scheme=h2c
logging:
driver: "json-file"
options:
max-size: "500m"
max-file: "2"
${proxy_service}
volumes:
netbird_traefik_data:
netbird_management:${proxy_volumes}
networks:
netbird:
driver: bridge
ipam:
config:
- subnet: 172.30.0.0/24
gateway: 172.30.0.1
EOF
return 0
}
render_npm_advanced_config() { render_npm_advanced_config() {
local upstream_host=$(get_upstream_host) local upstream_host=$(get_upstream_host)
local relay_addr="${upstream_host}:${RELAY_HOST_PORT}" local relay_addr="${upstream_host}:${RELAY_HOST_PORT}"
@@ -1424,6 +1836,36 @@ print_manual_instructions() {
return 0 return 0
} }
print_traefik_tcp_instructions() {
echo ""
echo "$MSG_SEPARATOR"
echo " TRAEFIK TCP PROXY SETUP"
echo "$MSG_SEPARATOR"
echo ""
echo "This configuration uses Traefik as a single entry point on port 443."
echo "Traefik handles TLS termination with Let's Encrypt and routes to services."
echo ""
echo "Open ports:"
echo " - 443/tcp (HTTPS - all NetBird services)"
echo " - $NETBIRD_STUN_PORT/udp (STUN - required for NAT traversal)"
echo ""
echo "Generated files:"
echo " - docker-compose.yml (container definitions with Traefik labels)"
if [[ "$ENABLE_PROXY" == "true" ]]; then
echo " - proxy.env (NetBird Proxy configuration)"
echo ""
echo "NetBird Proxy:"
echo " The proxy service is enabled and will be built from source."
echo " Any domain NOT matching $NETBIRD_DOMAIN will be passed through to the proxy."
echo " The proxy handles its own TLS certificates via ACME TLS-ALPN-01 challenge."
echo " Point your proxy domains (CNAMEs) to this server's IP address."
fi
echo ""
echo "You can access the NetBird dashboard at $NETBIRD_HTTP_PROTOCOL://$NETBIRD_DOMAIN"
echo "Follow the onboarding steps to set up your NetBird instance."
return 0
}
print_post_setup_instructions() { print_post_setup_instructions() {
case "$REVERSE_PROXY_TYPE" in case "$REVERSE_PROXY_TYPE" in
0) 0)
@@ -1444,6 +1886,9 @@ print_post_setup_instructions() {
5) 5)
print_manual_instructions print_manual_instructions
;; ;;
6)
print_traefik_tcp_instructions
;;
*) *)
echo "Unknown reverse proxy type: $REVERSE_PROXY_TYPE" > /dev/stderr echo "Unknown reverse proxy type: $REVERSE_PROXY_TYPE" > /dev/stderr
;; ;;

View File

@@ -0,0 +1,17 @@
FROM golang:1.25-bookworm AS builder
WORKDIR /app
# Install build dependencies
RUN apt-get update && apt-get install -y gcc libc6-dev && rm -rf /var/lib/apt/lists/*
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=1 GOOS=linux go build -ldflags="-s -w" -o netbird-mgmt ./management
FROM ubuntu:24.04
RUN apt update && apt install -y ca-certificates && rm -fr /var/cache/apt
ENTRYPOINT [ "/go/bin/netbird-mgmt","management"]
CMD ["--log-file", "console"]
COPY --from=builder /app/netbird-mgmt /go/bin/netbird-mgmt

View File

@@ -42,6 +42,7 @@ var (
acmeCerts bool acmeCerts bool
acmeAddr string acmeAddr string
acmeDir string acmeDir string
acmeChallengeType string
debugEndpoint bool debugEndpoint bool
debugEndpointAddr string debugEndpointAddr string
healthAddr string healthAddr string
@@ -72,9 +73,10 @@ func init() {
rootCmd.Flags().StringVar(&addr, "addr", envStringOrDefault("NB_PROXY_ADDRESS", ":443"), "Reverse proxy address to listen on") rootCmd.Flags().StringVar(&addr, "addr", envStringOrDefault("NB_PROXY_ADDRESS", ":443"), "Reverse proxy address to listen on")
rootCmd.Flags().StringVar(&proxyDomain, "domain", envStringOrDefault("NB_PROXY_DOMAIN", ""), "The Domain at which this proxy will be reached. e.g., netbird.example.com") rootCmd.Flags().StringVar(&proxyDomain, "domain", envStringOrDefault("NB_PROXY_DOMAIN", ""), "The Domain at which this proxy will be reached. e.g., netbird.example.com")
rootCmd.Flags().StringVar(&certDir, "cert-dir", envStringOrDefault("NB_PROXY_CERTIFICATE_DIRECTORY", "./certs"), "Directory to store certificates") rootCmd.Flags().StringVar(&certDir, "cert-dir", envStringOrDefault("NB_PROXY_CERTIFICATE_DIRECTORY", "./certs"), "Directory to store certificates")
rootCmd.Flags().BoolVar(&acmeCerts, "acme-certs", envBoolOrDefault("NB_PROXY_ACME_CERTIFICATES", false), "Generate ACME certificates using HTTP-01 challenges") rootCmd.Flags().BoolVar(&acmeCerts, "acme-certs", envBoolOrDefault("NB_PROXY_ACME_CERTIFICATES", false), "Generate ACME certificates automatically")
rootCmd.Flags().StringVar(&acmeAddr, "acme-addr", envStringOrDefault("NB_PROXY_ACME_ADDRESS", ":80"), "HTTP address for ACME HTTP-01 challenges") rootCmd.Flags().StringVar(&acmeAddr, "acme-addr", envStringOrDefault("NB_PROXY_ACME_ADDRESS", ":80"), "HTTP address for ACME HTTP-01 challenges (only used when acme-challenge-type is http-01)")
rootCmd.Flags().StringVar(&acmeDir, "acme-dir", envStringOrDefault("NB_PROXY_ACME_DIRECTORY", acme.LetsEncryptURL), "URL of ACME challenge directory") rootCmd.Flags().StringVar(&acmeDir, "acme-dir", envStringOrDefault("NB_PROXY_ACME_DIRECTORY", acme.LetsEncryptURL), "URL of ACME challenge directory")
rootCmd.Flags().StringVar(&acmeChallengeType, "acme-challenge-type", envStringOrDefault("NB_PROXY_ACME_CHALLENGE_TYPE", "tls-alpn-01"), "ACME challenge type: tls-alpn-01 (default, port 443 only) or http-01 (requires port 80)")
rootCmd.Flags().BoolVar(&debugEndpoint, "debug-endpoint", envBoolOrDefault("NB_PROXY_DEBUG_ENDPOINT", false), "Enable debug HTTP endpoint") rootCmd.Flags().BoolVar(&debugEndpoint, "debug-endpoint", envBoolOrDefault("NB_PROXY_DEBUG_ENDPOINT", false), "Enable debug HTTP endpoint")
rootCmd.Flags().StringVar(&debugEndpointAddr, "debug-endpoint-addr", envStringOrDefault("NB_PROXY_DEBUG_ENDPOINT_ADDRESS", "localhost:8444"), "Address for the debug HTTP endpoint") rootCmd.Flags().StringVar(&debugEndpointAddr, "debug-endpoint-addr", envStringOrDefault("NB_PROXY_DEBUG_ENDPOINT_ADDRESS", "localhost:8444"), "Address for the debug HTTP endpoint")
rootCmd.Flags().StringVar(&healthAddr, "health-addr", envStringOrDefault("NB_PROXY_HEALTH_ADDRESS", "localhost:8080"), "Address for the health probe endpoint (liveness/readiness/startup)") rootCmd.Flags().StringVar(&healthAddr, "health-addr", envStringOrDefault("NB_PROXY_HEALTH_ADDRESS", "localhost:8080"), "Address for the health probe endpoint (liveness/readiness/startup)")
@@ -151,6 +153,7 @@ func runServer(cmd *cobra.Command, args []string) error {
GenerateACMECertificates: acmeCerts, GenerateACMECertificates: acmeCerts,
ACMEChallengeAddress: acmeAddr, ACMEChallengeAddress: acmeAddr,
ACMEDirectory: acmeDir, ACMEDirectory: acmeDir,
ACMEChallengeType: acmeChallengeType,
DebugEndpointEnabled: debugEndpoint, DebugEndpointEnabled: debugEndpoint,
DebugEndpointAddress: debugEndpointAddr, DebugEndpointAddress: debugEndpointAddr,
HealthAddress: healthAddr, HealthAddress: healthAddr,

View File

@@ -77,6 +77,9 @@ type Server struct {
GenerateACMECertificates bool GenerateACMECertificates bool
ACMEChallengeAddress string ACMEChallengeAddress string
ACMEDirectory string ACMEDirectory string
// ACMEChallengeType specifies the ACME challenge type: "http-01" or "tls-alpn-01".
// Defaults to "tls-alpn-01" if not specified.
ACMEChallengeType string
// CertLockMethod controls how ACME certificate locks are coordinated // CertLockMethod controls how ACME certificate locks are coordinated
// across replicas. Default: CertLockAuto (detect environment). // across replicas. Default: CertLockAuto (detect environment).
CertLockMethod acme.CertLockMethod CertLockMethod acme.CertLockMethod
@@ -205,8 +208,18 @@ func (s *Server) ListenAndServe(ctx context.Context, addr string) (err error) {
// When generating ACME certificates, start a challenge server. // When generating ACME certificates, start a challenge server.
tlsConfig := &tls.Config{} tlsConfig := &tls.Config{}
if s.GenerateACMECertificates { if s.GenerateACMECertificates {
s.Logger.WithField("acme_server", s.ACMEDirectory).Debug("ACME certificates enabled, configuring certificate manager") // Default to TLS-ALPN-01 challenge if not specified
if s.ACMEChallengeType == "" {
s.ACMEChallengeType = "tls-alpn-01"
}
s.Logger.WithFields(log.Fields{
"acme_server": s.ACMEDirectory,
"challenge_type": s.ACMEChallengeType,
}).Debug("ACME certificates enabled, configuring certificate manager")
s.acme = acme.NewManager(s.CertificateDirectory, s.ACMEDirectory, s, s.Logger, s.CertLockMethod) s.acme = acme.NewManager(s.CertificateDirectory, s.ACMEDirectory, s, s.Logger, s.CertLockMethod)
// Only start HTTP server for HTTP-01 challenge type
if s.ACMEChallengeType == "http-01" {
s.http = &http.Server{ s.http = &http.Server{
Addr: s.ACMEChallengeAddress, Addr: s.ACMEChallengeAddress,
Handler: s.acme.HTTPHandler(nil), Handler: s.acme.HTTPHandler(nil),
@@ -216,6 +229,7 @@ func (s *Server) ListenAndServe(ctx context.Context, addr string) (err error) {
s.Logger.WithError(err).Error("ACME HTTP-01 challenge server failed") s.Logger.WithError(err).Error("ACME HTTP-01 challenge server failed")
} }
}() }()
}
tlsConfig = s.acme.TLSConfig() tlsConfig = s.acme.TLSConfig()
// ServerName needs to be set to allow for ACME to work correctly // ServerName needs to be set to allow for ACME to work correctly
@@ -224,7 +238,8 @@ func (s *Server) ListenAndServe(ctx context.Context, addr string) (err error) {
s.Logger.WithFields(log.Fields{ s.Logger.WithFields(log.Fields{
"ServerName": s.ProxyURL, "ServerName": s.ProxyURL,
}).Debug("started ACME challenge server") "challenge_type": s.ACMEChallengeType,
}).Debug("ACME certificate manager configured")
} else { } else {
s.Logger.Debug("ACME certificates disabled, using static certificates with file watching") s.Logger.Debug("ACME certificates disabled, using static certificates with file watching")
certPath := filepath.Join(s.CertificateDirectory, s.CertificateFile) certPath := filepath.Join(s.CertificateDirectory, s.CertificateFile)