# Static MUSL Build Guide for C Programs ## Overview This guide explains how to build truly portable static binaries using Alpine Linux and MUSL libc. These binaries have **zero runtime dependencies** and work on any Linux distribution without modification. This guide is specifically tailored for C programs that use: - **nostr_core_lib** - Nostr protocol implementation - **nostr_login_lite** - Nostr authentication library - Common dependencies: libwebsockets, OpenSSL, SQLite, curl, secp256k1 ## Why MUSL Static Binaries? ### Advantages Over glibc | Feature | MUSL Static | glibc Static | glibc Dynamic | |---------|-------------|--------------|---------------| | **Portability** | ✓ Any Linux | ⚠ glibc only | ✗ Requires matching libs | | **Binary Size** | ~7-10 MB | ~12-15 MB | ~2-3 MB | | **Dependencies** | None | NSS libs | Many system libs | | **Deployment** | Single file | Single file + NSS | Binary + libraries | | **Compatibility** | Universal | glibc version issues | Library version hell | ### Key Benefits 1. **True Portability**: Works on Alpine, Ubuntu, Debian, CentOS, Arch, etc. 2. **No Library Hell**: No `GLIBC_2.XX not found` errors 3. **Simple Deployment**: Just copy one file 4. **Reproducible Builds**: Same Docker image = same binary 5. **Security**: No dependency on system libraries with vulnerabilities ## Quick Start ### Prerequisites - Docker installed and running - Your C project with source code - Internet connection for downloading dependencies ### Basic Build Process ```bash # 1. Copy the Dockerfile template (see below) cp /path/to/c-relay/Dockerfile.alpine-musl ./Dockerfile.static # 2. Customize for your project (see Customization section) vim Dockerfile.static # 3. Build the static binary docker build --platform linux/amd64 -f Dockerfile.static -t my-app-builder . # 4. Extract the binary docker create --name temp-container my-app-builder docker cp temp-container:/build/my_app_static ./my_app_static docker rm temp-container # 5. Verify it's static ldd ./my_app_static # Should show "not a dynamic executable" ``` ## Dockerfile Template Here's a complete Dockerfile template you can customize for your project: ```dockerfile # Alpine-based MUSL static binary builder # Produces truly portable binaries with zero runtime dependencies FROM alpine:3.19 AS builder # Install build dependencies RUN apk add --no-cache \ build-base \ musl-dev \ git \ cmake \ pkgconfig \ autoconf \ automake \ libtool \ openssl-dev \ openssl-libs-static \ zlib-dev \ zlib-static \ curl-dev \ curl-static \ sqlite-dev \ sqlite-static \ linux-headers \ wget \ bash WORKDIR /build # Build libsecp256k1 static (required for Nostr) RUN cd /tmp && \ git clone https://github.com/bitcoin-core/secp256k1.git && \ cd secp256k1 && \ ./autogen.sh && \ ./configure --enable-static --disable-shared --prefix=/usr \ CFLAGS="-fPIC" && \ make -j$(nproc) && \ make install && \ rm -rf /tmp/secp256k1 # Build libwebsockets static (if needed for WebSocket support) RUN cd /tmp && \ git clone --depth 1 --branch v4.3.3 https://github.com/warmcat/libwebsockets.git && \ cd libwebsockets && \ mkdir build && cd build && \ cmake .. \ -DLWS_WITH_STATIC=ON \ -DLWS_WITH_SHARED=OFF \ -DLWS_WITH_SSL=ON \ -DLWS_WITHOUT_TESTAPPS=ON \ -DLWS_WITHOUT_TEST_SERVER=ON \ -DLWS_WITHOUT_TEST_CLIENT=ON \ -DLWS_WITHOUT_TEST_PING=ON \ -DLWS_WITH_HTTP2=OFF \ -DLWS_WITH_LIBUV=OFF \ -DLWS_WITH_LIBEVENT=OFF \ -DLWS_IPV6=ON \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=/usr \ -DCMAKE_C_FLAGS="-fPIC" && \ make -j$(nproc) && \ make install && \ rm -rf /tmp/libwebsockets # Copy git configuration for submodules COPY .gitmodules /build/.gitmodules COPY .git /build/.git # Initialize submodules RUN git submodule update --init --recursive # Copy and build nostr_core_lib COPY nostr_core_lib /build/nostr_core_lib/ RUN cd nostr_core_lib && \ chmod +x build.sh && \ sed -i 's/CFLAGS="-Wall -Wextra -std=c99 -fPIC -O2"/CFLAGS="-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -Wall -Wextra -std=c99 -fPIC -O2"/' build.sh && \ rm -f *.o *.a 2>/dev/null || true && \ ./build.sh --nips=1,6,13,17,19,44,59 # Copy and build nostr_login_lite (if used) # COPY nostr_login_lite /build/nostr_login_lite/ # RUN cd nostr_login_lite && make static # Copy your application source COPY src/ /build/src/ COPY Makefile /build/Makefile # Build your application with full static linking RUN gcc -static -O2 -Wall -Wextra -std=c99 \ -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 \ -I. -Inostr_core_lib -Inostr_core_lib/nostr_core \ -Inostr_core_lib/cjson -Inostr_core_lib/nostr_websocket \ src/*.c \ -o /build/my_app_static \ nostr_core_lib/libnostr_core_x64.a \ -lwebsockets -lssl -lcrypto -lsqlite3 -lsecp256k1 \ -lcurl -lz -lpthread -lm -ldl && \ strip /build/my_app_static # Verify it's truly static RUN echo "=== Binary Information ===" && \ file /build/my_app_static && \ ls -lh /build/my_app_static && \ echo "=== Checking for dynamic dependencies ===" && \ (ldd /build/my_app_static 2>&1 || echo "Binary is static") # Output stage - just the binary FROM scratch AS output COPY --from=builder /build/my_app_static /my_app_static ``` ## Customization Guide ### 1. Adjust Dependencies **Add dependencies** by modifying the `apk add` section: ```dockerfile RUN apk add --no-cache \ build-base \ musl-dev \ # Add your dependencies here: libpng-dev \ libpng-static \ libjpeg-turbo-dev \ libjpeg-turbo-static ``` **Remove unused dependencies** to speed up builds: - Remove `libwebsockets` section if you don't need WebSocket support - Remove `sqlite` if you don't use databases - Remove `curl` if you don't make HTTP requests ### 2. Configure nostr_core_lib NIPs Specify which NIPs your application needs: ```bash ./build.sh --nips=1,6,19 # Minimal: Basic protocol, keys, bech32 ./build.sh --nips=1,6,13,17,19,44,59 # Full: All common NIPs ./build.sh --nips=all # Everything available ``` **Common NIP combinations:** - **Basic client**: `1,6,19` (events, keys, bech32) - **With encryption**: `1,6,19,44` (add modern encryption) - **With DMs**: `1,6,17,19,44,59` (add private messages) - **Relay/server**: `1,6,13,17,19,42,44,59` (add PoW, auth) ### 3. Modify Compilation Flags **For your application:** ```dockerfile RUN gcc -static -O2 -Wall -Wextra -std=c99 \ -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 \ # REQUIRED for MUSL -I. -Inostr_core_lib \ # Include paths src/*.c \ # Your source files -o /build/my_app_static \ # Output binary nostr_core_lib/libnostr_core_x64.a \ # Nostr library -lwebsockets -lssl -lcrypto \ # Link libraries -lsqlite3 -lsecp256k1 -lcurl \ -lz -lpthread -lm -ldl ``` **Debug build** (with symbols, no optimization): ```dockerfile RUN gcc -static -g -O0 -DDEBUG \ -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 \ # ... rest of flags ``` ### 4. Multi-Architecture Support Build for different architectures: ```bash # x86_64 (Intel/AMD) docker build --platform linux/amd64 -f Dockerfile.static -t my-app-x86 . # ARM64 (Apple Silicon, Raspberry Pi 4+) docker build --platform linux/arm64 -f Dockerfile.static -t my-app-arm64 . ``` ## Build Script Template Create a `build_static.sh` script for convenience: ```bash #!/bin/bash set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" BUILD_DIR="$SCRIPT_DIR/build" DOCKERFILE="$SCRIPT_DIR/Dockerfile.static" # Detect architecture ARCH=$(uname -m) case "$ARCH" in x86_64) PLATFORM="linux/amd64" OUTPUT_NAME="my_app_static_x86_64" ;; aarch64|arm64) PLATFORM="linux/arm64" OUTPUT_NAME="my_app_static_arm64" ;; *) echo "Unknown architecture: $ARCH" exit 1 ;; esac echo "Building for platform: $PLATFORM" mkdir -p "$BUILD_DIR" # Build Docker image docker build \ --platform "$PLATFORM" \ -f "$DOCKERFILE" \ -t my-app-builder:latest \ --progress=plain \ . # Extract binary CONTAINER_ID=$(docker create my-app-builder:latest) docker cp "$CONTAINER_ID:/build/my_app_static" "$BUILD_DIR/$OUTPUT_NAME" docker rm "$CONTAINER_ID" chmod +x "$BUILD_DIR/$OUTPUT_NAME" echo "✓ Build complete: $BUILD_DIR/$OUTPUT_NAME" echo "✓ Size: $(du -h "$BUILD_DIR/$OUTPUT_NAME" | cut -f1)" # Verify if ldd "$BUILD_DIR/$OUTPUT_NAME" 2>&1 | grep -q "not a dynamic executable"; then echo "✓ Binary is fully static" else echo "⚠ Warning: Binary may have dynamic dependencies" fi ``` Make it executable: ```bash chmod +x build_static.sh ./build_static.sh ``` ## Common Issues and Solutions ### Issue 1: Fortification Errors **Error:** ``` undefined reference to '__snprintf_chk' undefined reference to '__fprintf_chk' ``` **Cause**: GCC's `-O2` enables fortification by default, which uses glibc-specific functions. **Solution**: Add these flags to **all** compilation commands: ```bash -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 ``` This must be applied to: 1. nostr_core_lib build.sh 2. Your application compilation 3. Any other libraries you build ### Issue 2: Missing Symbols from nostr_core_lib **Error:** ``` undefined reference to 'nostr_create_event' undefined reference to 'nostr_sign_event' ``` **Cause**: Required NIPs not included in nostr_core_lib build. **Solution**: Add missing NIPs: ```bash ./build.sh --nips=1,6,19 # Add the NIPs you need ``` ### Issue 3: Docker Permission Denied **Error:** ``` permission denied while trying to connect to the Docker daemon socket ``` **Solution**: ```bash sudo usermod -aG docker $USER newgrp docker # Or logout and login ``` ### Issue 4: Binary Won't Run on Target System **Checks**: ```bash # 1. Verify it's static ldd my_app_static # Should show "not a dynamic executable" # 2. Check architecture file my_app_static # Should match target system # 3. Test on different distributions docker run --rm -v $(pwd):/app alpine:latest /app/my_app_static --version docker run --rm -v $(pwd):/app ubuntu:latest /app/my_app_static --version ``` ## Project Structure Example Organize your project for easy static builds: ``` my-nostr-app/ ├── src/ │ ├── main.c │ ├── handlers.c │ └── utils.c ├── nostr_core_lib/ # Git submodule ├── nostr_login_lite/ # Git submodule (if used) ├── Dockerfile.static # Static build Dockerfile ├── build_static.sh # Build script ├── Makefile # Regular build └── README.md ``` ### Makefile Integration Add static build targets to your Makefile: ```makefile # Regular dynamic build all: my_app my_app: src/*.c gcc -O2 src/*.c -o my_app \ nostr_core_lib/libnostr_core_x64.a \ -lssl -lcrypto -lsecp256k1 -lz -lpthread -lm # Static MUSL build via Docker static: ./build_static.sh # Clean clean: rm -f my_app build/my_app_static_* .PHONY: all static clean ``` ## Deployment ### Single Binary Deployment ```bash # Copy to server scp build/my_app_static_x86_64 user@server:/opt/my-app/ # Run (no dependencies needed!) ssh user@server /opt/my-app/my_app_static_x86_64 ``` ### SystemD Service ```ini [Unit] Description=My Nostr Application After=network.target [Service] Type=simple User=myapp WorkingDirectory=/opt/my-app ExecStart=/opt/my-app/my_app_static_x86_64 Restart=always RestartSec=5 [Install] WantedBy=multi-user.target ``` ### Docker Container (Minimal) ```dockerfile FROM scratch COPY my_app_static_x86_64 /app ENTRYPOINT ["/app"] ``` Build and run: ```bash docker build -t my-app:latest . docker run --rm my-app:latest --help ``` ## Reusing c-relay Files You can directly copy these files from c-relay: ### 1. Dockerfile.alpine-musl ```bash cp /path/to/c-relay/Dockerfile.alpine-musl ./Dockerfile.static ``` Then customize: - Change binary name (line 125) - Adjust source files (line 122-124) - Modify include paths (line 120-121) ### 2. build_static.sh ```bash cp /path/to/c-relay/build_static.sh ./ ``` Then customize: - Change `OUTPUT_NAME` variable (lines 66, 70) - Update Docker image name (line 98) - Modify verification commands (lines 180-184) ### 3. .dockerignore (Optional) ```bash cp /path/to/c-relay/.dockerignore ./ ``` Helps speed up Docker builds by excluding unnecessary files. ## Best Practices 1. **Version Control**: Commit your Dockerfile and build script 2. **Tag Builds**: Include git commit hash in binary version 3. **Test Thoroughly**: Verify on multiple distributions 4. **Document Dependencies**: List required NIPs and libraries 5. **Automate**: Use CI/CD to build on every commit 6. **Archive Binaries**: Keep old versions for rollback ## Performance Comparison | Metric | MUSL Static | glibc Dynamic | |--------|-------------|---------------| | Binary Size | 7-10 MB | 2-3 MB + libs | | Startup Time | ~50ms | ~40ms | | Memory Usage | Similar | Similar | | Portability | ✓ Universal | ✗ System-dependent | | Deployment | Single file | Binary + libraries | ## References - [MUSL libc](https://musl.libc.org/) - [Alpine Linux](https://alpinelinux.org/) - [nostr_core_lib](https://github.com/chebizarro/nostr_core_lib) - [Static Linking Best Practices](https://www.musl-libc.org/faq.html) - [c-relay Implementation](./docs/musl_static_build.md) ## Example: Minimal Nostr Client Here's a complete example of building a minimal Nostr client: ```c // minimal_client.c #include "nostr_core/nostr_core.h" #include int main() { // Generate keypair char nsec[64], npub[64]; nostr_generate_keypair(nsec, npub); printf("Generated keypair:\n"); printf("Private key (nsec): %s\n", nsec); printf("Public key (npub): %s\n", npub); // Create event cJSON *event = nostr_create_event(1, "Hello, Nostr!", NULL); nostr_sign_event(event, nsec); char *json = cJSON_Print(event); printf("\nSigned event:\n%s\n", json); free(json); cJSON_Delete(event); return 0; } ``` **Dockerfile.static:** ```dockerfile FROM alpine:3.19 AS builder RUN apk add --no-cache build-base musl-dev git autoconf automake libtool \ openssl-dev openssl-libs-static zlib-dev zlib-static WORKDIR /build # Build secp256k1 RUN cd /tmp && git clone https://github.com/bitcoin-core/secp256k1.git && \ cd secp256k1 && ./autogen.sh && \ ./configure --enable-static --disable-shared --prefix=/usr CFLAGS="-fPIC" && \ make -j$(nproc) && make install # Copy and build nostr_core_lib COPY nostr_core_lib /build/nostr_core_lib/ RUN cd nostr_core_lib && \ sed -i 's/CFLAGS="-Wall/CFLAGS="-U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -Wall/' build.sh && \ ./build.sh --nips=1,6,19 # Build application COPY minimal_client.c /build/ RUN gcc -static -O2 -Wall -std=c99 \ -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 \ -Inostr_core_lib -Inostr_core_lib/nostr_core -Inostr_core_lib/cjson \ minimal_client.c -o /build/minimal_client_static \ nostr_core_lib/libnostr_core_x64.a \ -lssl -lcrypto -lsecp256k1 -lz -lpthread -lm -ldl && \ strip /build/minimal_client_static FROM scratch COPY --from=builder /build/minimal_client_static /minimal_client_static ``` **Build and run:** ```bash docker build -f Dockerfile.static -t minimal-client . docker create --name temp minimal-client docker cp temp:/minimal_client_static ./ docker rm temp ./minimal_client_static ``` ## Conclusion Static MUSL binaries provide the best portability for C applications. While they're slightly larger than dynamic binaries, the benefits of zero dependencies and universal compatibility make them ideal for: - Server deployments across different Linux distributions - Embedded systems and IoT devices - Docker containers (FROM scratch) - Distribution to users without dependency management - Long-term archival and reproducibility Follow this guide to create portable, self-contained binaries for your Nostr applications!