v0.1.18 - Add automatic cleanup of dynamic build artifacts after static builds

This commit is contained in:
Your Name
2025-12-13 07:54:20 -04:00
parent 64b9f28444
commit a5f92e4da3
18 changed files with 2239 additions and 18 deletions

296
docs/STATIC_BUILD.md Normal file
View File

@@ -0,0 +1,296 @@
# Ginxsom Static MUSL Build Guide
This guide explains how to build and deploy Ginxsom as a fully static MUSL binary with zero runtime dependencies.
## Overview
Ginxsom now supports building as a static MUSL binary using Alpine Linux and Docker. This produces a truly portable binary that works on **any Linux distribution** without requiring any system libraries.
## Benefits
| Feature | Static MUSL | Dynamic glibc |
|---------|-------------|---------------|
| **Portability** | ✓ Any Linux | ✗ Requires matching libs |
| **Dependencies** | None | libfcgi, libsqlite3, etc. |
| **Deployment** | Copy one file | Build on target |
| **Binary Size** | ~7-10 MB | ~2-3 MB + libraries |
| **Deployment Time** | ~10 seconds | ~5-10 minutes |
## Prerequisites
- Docker installed and running
- Internet connection (for first build only)
- ~2GB disk space for Docker images
## Quick Start
### 1. Build Static Binary
```bash
# Build production binary (optimized, stripped)
make static
# Or build debug binary (with symbols)
make static-debug
# Or use the script directly
./build_static.sh
./build_static.sh --debug
```
The binary will be created in `build/ginxsom-fcgi_static_x86_64` (or `_arm64` for ARM systems).
### 2. Verify Binary
```bash
# Check if truly static
ldd build/ginxsom-fcgi_static_x86_64
# Should output: "not a dynamic executable"
# Check file info
file build/ginxsom-fcgi_static_x86_64
# Should show: "statically linked"
# Check size
ls -lh build/ginxsom-fcgi_static_x86_64
```
### 3. Deploy to Server
```bash
# Use the simplified deployment script
./deploy_static.sh
# Or manually copy and start
scp build/ginxsom-fcgi_static_x86_64 user@server:/path/to/ginxsom/
ssh user@server
chmod +x /path/to/ginxsom/ginxsom-fcgi_static_x86_64
sudo spawn-fcgi -M 666 -u www-data -g www-data \
-s /tmp/ginxsom-fcgi.sock \
-- /path/to/ginxsom/ginxsom-fcgi_static_x86_64 \
--db-path /path/to/db/ginxsom.db \
--storage-dir /var/www/html/blossom
```
## Build Process Details
### What Happens During Build
1. **Docker Image Creation** (5-10 minutes first time, cached after):
- Uses Alpine Linux 3.19 (native MUSL)
- Builds secp256k1 statically
- Builds nostr_core_lib with required NIPs
- Embeds web interface files
- Compiles Ginxsom with full static linking
2. **Binary Extraction**:
- Extracts binary from Docker container
- Verifies static linking
- Makes executable
3. **Verification**:
- Checks for dynamic dependencies
- Reports file size
- Tests execution
### Docker Layers (Cached)
The Dockerfile uses multi-stage builds with caching:
```
Layer 1: Alpine base + dependencies (cached)
Layer 2: Build secp256k1 (cached)
Layer 3: Initialize git submodules (cached unless .gitmodules changes)
Layer 4: Build nostr_core_lib (cached unless nostr_core_lib changes)
Layer 5: Embed web files (cached unless api/ changes)
Layer 6: Build Ginxsom (rebuilds when src/ changes)
```
This means subsequent builds are **much faster** (~1-2 minutes) since only changed layers rebuild.
## Deployment Comparison
### Old Dynamic Build Deployment
```bash
# 1. Sync entire project (30 seconds)
rsync -avz . user@server:/path/
# 2. Build on remote server (5-10 minutes)
ssh user@server "cd /path && make clean && make"
# 3. Restart service (10 seconds)
ssh user@server "sudo systemctl restart ginxsom"
# Total: ~6-11 minutes
```
### New Static Build Deployment
```bash
# 1. Build locally once (5-10 minutes first time, cached after)
make static
# 2. Copy binary (10 seconds)
scp build/ginxsom-fcgi_static_x86_64 user@server:/path/
# 3. Restart service (10 seconds)
ssh user@server "sudo systemctl restart ginxsom"
# Total: ~20 seconds (after first build)
```
## Cleanup
### Automatic Cleanup
The static build script automatically cleans up old dynamic build artifacts (`.o` files and `ginxsom-fcgi` binary) after successfully building the static binary. This keeps your `build/` directory clean.
### Manual Cleanup
```bash
# Clean dynamic build artifacts (preserves static binaries)
make clean
# Clean everything including static binaries
make clean-all
# Or manually remove specific files
rm -f build/*.o
rm -f build/ginxsom-fcgi
rm -f build/ginxsom-fcgi_static_*
```
## Troubleshooting
### Docker Not Found
```bash
# Install Docker
sudo apt install docker.io
# Add user to docker group
sudo usermod -aG docker $USER
newgrp docker
```
### Build Fails
```bash
# Clean Docker cache and rebuild
docker system prune -a
make static
```
### Binary Won't Run on Target
```bash
# Verify it's static
ldd build/ginxsom-fcgi_static_x86_64
# Check architecture matches
file build/ginxsom-fcgi_static_x86_64
uname -m # On target system
```
### Alpine Package Not Found
If you get errors about missing Alpine packages, the package name may have changed. Check Alpine's package database:
- https://pkgs.alpinelinux.org/packages
## Advanced Usage
### Cross-Compilation
Build for different architectures:
```bash
# Build for ARM64 on x86_64 machine
docker build --platform linux/arm64 -f Dockerfile.alpine-musl -t ginxsom-arm64 .
```
### Custom NIPs
Edit `Dockerfile.alpine-musl` line 66 to change which NIPs are included:
```dockerfile
./build.sh --nips=1,6,19 # Minimal
./build.sh --nips=1,6,13,17,19,44,59 # Full (default)
```
### Debug Build
```bash
# Build with debug symbols (no optimization)
make static-debug
# Binary will be larger but include debugging info
gdb build/ginxsom-fcgi_static_x86_64
```
## File Structure
```
ginxsom/
├── Dockerfile.alpine-musl # Alpine Docker build definition
├── build_static.sh # Build script wrapper
├── deploy_static.sh # Simplified deployment script
├── Makefile # Updated with 'static' target
└── build/
└── ginxsom-fcgi_static_x86_64 # Output binary
```
## CI/CD Integration
### GitHub Actions Example
```yaml
name: Build Static Binary
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: recursive
- name: Build static binary
run: make static
- name: Upload artifact
uses: actions/upload-artifact@v2
with:
name: ginxsom-static
path: build/ginxsom-fcgi_static_x86_64
```
## Performance
Static MUSL binaries have minimal performance impact:
| Metric | Static MUSL | Dynamic glibc |
|--------|-------------|---------------|
| Startup Time | ~50ms | ~40ms |
| Memory Usage | Similar | Similar |
| Request Latency | Identical | Identical |
| Binary Size | 7-10 MB | 2-3 MB + libs |
The slight startup delay is negligible for a long-running FastCGI process.
## References
- [MUSL libc](https://musl.libc.org/)
- [Alpine Linux](https://alpinelinux.org/)
- [Static Linking Best Practices](https://www.musl-libc.org/faq.html)
- [c-relay Static Build](../c-relay/STATIC_BUILD.md)
## Support
For issues with static builds:
1. Check Docker is running: `docker info`
2. Verify submodules: `git submodule status`
3. Clean and rebuild: `docker system prune -a && make static`
4. Check logs in Docker build output

612
docs/STATIC_MUSL_GUIDE.md Normal file
View File

@@ -0,0 +1,612 @@
# 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 <stdio.h>
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!