Skip to content

Native Dependencies

This recipe explains how to manage native Node.js modules like sharp and bcrypt on Azure App Service using multi-stage Docker builds.

flowchart TD
    A[Source with native modules] --> B[Builder image with compilers]
    B --> C[npm install + build]
    C --> D[Runtime image with minimal libs]
    D --> E[Deploy to App Service]
    E --> F[Verify module load succeeds]

Overview

Native modules contain C/C++ code that must be compiled for the target operating system and architecture. When deploying to Azure App Service (Linux), compiling these locally (e.g., on Windows or macOS) can result in runtime errors like ELF header invalid.

Prerequisites

  • Azure App Service (Linux)
  • Docker Desktop (for building custom containers)

Implementation

1. Build-time vs Runtime Dependencies

Some modules (like sharp) require system-level libraries during compilation and execution.

  • sharp: Requires libvips and related development headers.
  • bcrypt: Requires python3, make, and g++ for compilation.

2. Multi-stage Docker Build for Native Modules

A multi-stage build allows you to use a heavy build image with all the compilation tools and copy only the final artifacts to a slim runtime image.

# Stage 1: Build
FROM node:20-bookworm AS builder
WORKDIR /app

# Install compilation tools
RUN apt-get update && apt-get install -y \
    build-essential \
    python3 \
    libvips-dev \
    && rm -rf /var/lib/apt/lists/*

COPY package*.json ./
RUN npm install --include=dev

COPY . .
RUN npm run build

# Stage 2: Runtime
FROM node:20-bookworm-slim
WORKDIR /app

# Install runtime-only library dependencies (e.g., for sharp)
RUN apt-get update && apt-get install -y \
    libvips \
    && rm -rf /var/lib/apt/lists/*

# Copy only the production node_modules and built assets
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist

EXPOSE 8080
CMD ["node", "dist/index.js"]

3. Alternative for Zip Deployments

If you are not using containers and instead using Zip Deploy, ensure you use Remote Build so the compilation happens on the Linux App Service server. Set SCM_DO_BUILD_DURING_DEPLOYMENT=true in App Settings, then deploy:

az webapp deploy --resource-group $RG --name $APP_NAME --src-path <zip-file> --type zip --output json

Verification

After deployment, test functionality by triggering an image resize with sharp or a password hash with bcrypt.

# Log into the App Service SSH terminal and check for errors
# https://${APP_NAME}.scm.azurewebsites.net/webssh/host
node -e "require('sharp')"
node -e "require('bcrypt')"

Troubleshooting

  • Error: ELF header invalid: This usually means the module was compiled on a different OS (e.g., Windows) and copied to Linux. Re-run npm install on the target platform or use Docker.
  • Missing shared libraries (.so files): If sharp fails with libvips.so.42 not found, ensure you installed the runtime dependencies (libvips) in your Dockerfile.
  • Compilation Failures: Ensure the build stage has build-essential, python3, and make.

Advanced Topics

Run It in the Portal

Portal view: Log stream blade (verifying native dependencies loaded at runtime)

Log stream blade for a Web App showing the live runtime log feed. The top filter bar has "Log Level" dropdown, "Stop" / "Copy" / "Clear" buttons, a "Logs" radio set with "Runtime" selected (Platform unselected), an Instances selector with a worker instance ID, and a Lookback period of "Last 30 minutes". The main panel renders a dark console with time-stamped log lines emitted by an azure.core.pipeline.policies.http_logging_policy showing Request URL, Request method (POST), Request headers (Content-Type, Content-Length, Accept, x-ms-client-request-id, User-Agent), Response status 200, and "Transmission succeeded: Item received: 3. Items accepted: 3" entries from the Azure Monitor OpenTelemetry exporter. The left navigation shows Log stream selected under the Monitoring group.

The Log stream blade is the Portal surface for confirming that native Node.js modules installed by this recipe — sharp, bcrypt, and their libvips-related native libraries — loaded correctly during App Service startup. With Runtime selected and lookback set to Last 30 minutes, the live console here shows the same output you would otherwise tail with az webapp log tail. The example feed visible here was captured from a Python deployment, so the Transmission succeeded: Item received: 3. Items accepted: 3 line is OpenTelemetry Python exporter output; for the Node.js recipe, the equivalent signal in this same blade is the absence of ELF header invalid or libvips.so.42 not found messages that the Troubleshooting section on this page calls out. Use this blade after the recipe's multi-stage Docker build to verify no native-library failure is logged during the first request after restart.

See Also

Sources