9.8 KiB
NIP Static Build Guide
Overview
This guide explains how to build NIP as a fully static binary for minimal deployment scenarios. Static builds are essential for the "minimal install philosophy" - enabling users to boot a tiny netinst image and build their perfect system from a single ~5MB binary.
Why Static Linking?
Benefits
- Zero Dependencies: Binary runs on any Linux system with kernel 4.19+
- Minimal Deployment: Single file, no package manager needed
- Predictable Behavior: No library version conflicts
- Portable: Works across different distributions
- Netinst Ready: Perfect for minimal installation images
Trade-offs
- Larger Binary Size: ~5-10MB vs ~2-3MB dynamic
- No Shared Libraries: Can't benefit from system library updates
- Build Complexity: Requires careful configuration
Build Methods
Method 1: Using build_static.sh (Recommended)
The easiest way to build a static binary:
cd nip
./build_static.sh
This script:
- Detects musl-gcc for optimal builds
- Configures all static linking flags
- Verifies the binary is truly static
- Creates deployment package
- Provides size comparison
Output:
nip-static- Fully static binarynip-v0.2.0-weihnachtsmann-static-*.tar.gz- Deployment package
Method 2: Manual Build with Musl
For the smallest possible binary (recommended):
# Install musl-tools (Debian/Ubuntu)
sudo apt install musl-tools
# Or on Arch
sudo pacman -S musl
# Build with musl
nim c \
--define:static \
--define:release \
--define:danger \
--opt:speed \
--mm:orc \
--threads:on \
--passC:-flto \
--passL:-flto \
--passL:-static \
--passL:-s \
--gcc.exe:musl-gcc \
--gcc.linkerexe:musl-gcc \
--out:nip-static \
nip.nim
Expected Size: ~5-7MB
Method 3: Manual Build with Glibc
For maximum compatibility (larger binary):
nim c \
--define:static \
--define:release \
--define:danger \
--opt:speed \
--mm:orc \
--threads:on \
--passC:-flto \
--passL:-flto \
--passL:-static \
--passL:-static-libgcc \
--passL:-s \
--out:nip-static \
nip.nim
Expected Size: ~8-12MB
Configuration Details
Static Linking Flags
The config.nims file includes static linking configuration when -d:static is defined:
when defined(static):
# Core static linking
switch("passL", "-static")
switch("passL", "-static-libgcc")
# Use musl if available
when defined(linux):
if fileExists("/usr/lib/x86_64-linux-musl/libc.a"):
switch("gcc.exe", "musl-gcc")
switch("gcc.linkerexe", "musl-gcc")
# Optimization flags
switch("passL", "-s") # Strip symbols
switch("passC", "-flto") # Link-time optimization
switch("passL", "-flto")
switch("passC", "-ffunction-sections") # Section garbage collection
switch("passC", "-fdata-sections")
switch("passL", "-Wl,--gc-sections")
Compiler Flags Explained
| Flag | Purpose | Impact |
|---|---|---|
--define:static |
Enable static build mode | Activates static config |
--define:release |
Release optimizations | Faster code |
--define:danger |
Disable runtime checks | Smaller, faster |
--opt:speed |
Optimize for speed | Better performance |
--mm:orc |
Use ORC memory manager | Deterministic GC |
--threads:on |
Enable threading | Parallel operations |
--passC:-flto |
Link-time optimization (C) | Better code generation |
--passL:-flto |
Link-time optimization (linker) | Smaller binary |
--passL:-static |
Static linking | No dynamic deps |
--passL:-s |
Strip symbols | Smaller binary |
--gcc.exe:musl-gcc |
Use musl compiler | Smaller libc |
Verification
Check Static Linking
# Should output: "not a dynamic executable"
ldd nip-static
# Or on some systems: "statically linked"
file nip-static
Check Binary Size
ls -lh nip-static
# Target: 5-10MB
Test Functionality
# Basic test (may require root)
./nip-static --version
# Full test
sudo ./nip-static setup
sudo ./nip-static status
Platform-Specific Notes
Linux (x86_64)
Recommended: Use musl-gcc for smallest binaries
# Debian/Ubuntu
sudo apt install musl-tools
# Arch
sudo pacman -S musl
# Fedora
sudo dnf install musl-gcc
Linux (ARM64)
# Install cross-compilation tools
sudo apt install gcc-aarch64-linux-gnu musl-tools
# Build for ARM64
nim c \
--cpu:arm64 \
--os:linux \
--define:static \
--gcc.exe:aarch64-linux-gnu-gcc \
--gcc.linkerexe:aarch64-linux-gnu-gcc \
--passL:-static \
nip.nim
Linux (RISC-V)
# Install RISC-V toolchain
sudo apt install gcc-riscv64-linux-gnu
# Build for RISC-V
nim c \
--cpu:riscv64 \
--os:linux \
--define:static \
--gcc.exe:riscv64-linux-gnu-gcc \
nip.nim
Troubleshooting
Problem: Binary has dynamic dependencies
Symptom:
ldd nip-static
# Shows: linux-vdso.so.1, libc.so.6, etc.
Solution:
- Ensure
--passL:-staticis used - Check for dynamic library overrides
- Use musl-gcc instead of gcc
- Verify no
-lflags without static variants
Problem: Binary is too large (>15MB)
Causes:
- Not using musl (glibc is larger)
- Debug symbols not stripped
- LTO not enabled
Solutions:
# Strip manually if needed
strip nip-static
# Verify LTO is enabled
nim c --define:static --passC:-flto --passL:-flto ...
# Use musl
nim c --define:static --gcc.exe:musl-gcc ...
Problem: "undefined reference" errors
Symptom:
/usr/bin/ld: undefined reference to `pthread_create'
Solution: Add threading library explicitly:
nim c --define:static --passL:-lpthread ...
Problem: SSL/TLS not working
Symptom:
Error: SSL library not found
Solution: Ensure static SSL libraries are available:
# Debian/Ubuntu
sudo apt install libssl-dev
# Build with SSL
nim c --define:static --define:ssl --passL:-lssl --passL:-lcrypto ...
Size Optimization Tips
1. Use Musl Instead of Glibc
Savings: ~3-5MB
nim c --define:static --gcc.exe:musl-gcc nip.nim
2. Enable Link-Time Optimization
Savings: ~1-2MB
nim c --define:static --passC:-flto --passL:-flto nip.nim
3. Strip Symbols
Savings: ~500KB-1MB
nim c --define:static --passL:-s nip.nim
# Or manually: strip nip-static
4. Enable Section Garbage Collection
Savings: ~500KB
nim c \
--define:static \
--passC:-ffunction-sections \
--passC:-fda-sections \
--passL:-Wl,--gc-sections \
nip.nim
5. Use UPX Compression (Optional)
Savings: ~50-70% (but slower startup)
# Install UPX
sudo apt install upx-ucl
# Compress binary
upx --best --lzma nip-static
# Result: ~2-3MB compressed
Trade-off: Slower startup time (~100-200ms decompression)
Deployment
Minimal Netinst Image
Create a minimal bootable image with just NIP:
# 1. Create minimal rootfs
mkdir -p netinst/{bin,lib,etc,usr/local/bin}
# 2. Copy static binary
cp nip-static netinst/usr/local/bin/nip
# 3. Add minimal init
cat > netinst/init << 'EOF'
#!/bin/sh
exec /usr/local/bin/nip setup
EOF
chmod +x netinst/init
# 4. Create initramfs
cd netinst
find . | cpio -o -H newc | gzip > ../netinst.img
Result: ~50-100MB bootable image with NIP
Installation Script
The static build includes a minimal install script:
#!/bin/bash
# Ultra-minimal NIP installation
sudo cp nip /usr/local/bin/nip
sudo chmod +x /usr/local/bin/nip
sudo nip setup
CI/CD Integration
GitHub Actions Example
name: Build Static Binary
on: [push, pull_request]
jobs:
build-static:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Nim
uses: jiro4989/setup-nim-action@v1
with:
nim-version: '2.0.0'
- name: Install musl-tools
run: sudo apt-get install -y musl-tools
- name: Build static binary
run: |
cd nip
./build_static.sh
- name: Verify static linking
run: |
ldd nip/nip-static || echo "Fully static"
- name: Upload artifact
uses: actions/upload-artifact@v3
with:
name: nip-static
path: nip/nip-static
Performance Considerations
Startup Time
- Dynamic binary: ~10-20ms
- Static binary (uncompressed): ~15-30ms
- Static binary (UPX compressed): ~100-200ms
Runtime Performance
Static binaries have identical runtime performance to dynamic binaries. The only difference is startup time.
Memory Usage
Static binaries may use slightly more memory (~1-2MB) because:
- Entire libc is included
- No shared library benefits
For NIP's use case (system package manager), this is negligible.
Best Practices
1. Always Verify Static Linking
ldd nip-static | grep -q "not a dynamic" || echo "WARNING: Not fully static"
2. Test on Minimal Systems
# Test in Docker with minimal base
docker run --rm -v $(pwd):/app alpine:latest /app/nip-static --version
3. Document Dependencies
Even static binaries have kernel requirements:
- Linux kernel 4.19+ (for modern syscalls)
- x86_64, ARM64, or RISC-V architecture
4. Provide Multiple Variants
Offer both static and dynamic builds:
- Static: For minimal installs, netboot, embedded
- Dynamic: For regular systems, faster startup
References
Document Version: 1.0 Last Updated: November 18, 2025 Applies To: NIP v0.2.0 "Weihnachtsmann"