Skipping Husky in CI and production installs Jump to heading

Husky installs itself through npm’s prepare lifecycle script, which runs after every npm install and npm ci. On a developer machine that is exactly right. Elsewhere it causes three distinct problems. In CI, installing hooks is pointless, since nobody commits there, and occasionally harmful when a pipeline does commit and hooks fire unexpectedly. In Docker builds and production installs with --omit=dev, Husky is not installed, so prepare fails with husky: not found and the build breaks. And in published packages, consumers’ installs may run your prepare script. This page covers each environment with the smallest reliable fix, within local hook configuration with Husky.

When to use this approach Jump to heading

  • Production or Docker builds fail with sh: husky: not found during npm ci --omit=dev.
  • CI pipelines that create commits run your local hooks unexpectedly.
  • You publish a package to a registry and its prepare script runs Husky.
  • You set up Husky recently, as in setting up Husky in a new project, and are preparing it for deployment.

Step 1 β€” Understand when prepare runs Jump to heading

prepare runs after local npm install and npm ci, before npm publish, and when a package is installed from a Git URL. It does not run when a published package is installed from the registry as a dependency.

Where the prepare script runs HuskyOn developer machines the prepare script should run Husky. In CI it runs but is pointless. In production installs that omit dev dependencies it runs and fails because Husky is not installed. Registry consumers of a published package do not run it.prepare runs?Should Husky run?developer npm installyesyesCI npm ciyesnonpm ci --omit=devyes, then failsnoregistry dependency installnon/athe failing row is the one that breaks deploys

Step 2 β€” Guard the prepare script Jump to heading

The simplest robust fix makes prepare tolerate Husky being absent. This stops production installs failing without changing developer behaviour.

{
  "scripts": {
    "prepare": "husky || true"
  }
}

A slightly stricter variant only skips when Husky is genuinely not installed, so a real Husky error on a developer machine is still visible:

{
  "scripts": {
    "prepare": "node -e \"try { require.resolve('husky') } catch { process.exit(0) }\" && husky || true"
  }
}
# Verification: a production-style install no longer fails
rm -rf node_modules && npm ci --omit=dev && echo "install ok"

Step 3 β€” Disable hooks in CI with HUSKY=0 Jump to heading

Husky v9 skips installation when the HUSKY environment variable is 0. Set it globally in CI so pipelines never install or run hooks.

# GitHub Actions
env:
  HUSKY: 0
# GitLab CI
variables:
  HUSKY: "0"

HUSKY=0 also disables already-installed hooks at run time, which matters for pipelines that commit β€” a release job bumping a version, a bot updating a lockfile. Those commits should be checked by CI’s own steps, not by developer hooks that assume a developer environment.

Husky across the environmentsDeveloper installs run prepare and activate hooks. CI sets HUSKY to zero, so installs skip hook setup and any commits made by pipelines do not run developer hooks. Production installs omit dev dependencies, and the guarded prepare script exits quietly.Developerprepare β†’ huskyhooks activeCIHUSKY=0no hooksProduction--omit=devprepare guardedone environment variable and one guarded script cover every case

Step 4 β€” Fix Docker builds Jump to heading

Multi-stage Docker builds often run npm ci twice: once with dev dependencies to build, once without for the runtime image. The second fails without a guard. Besides the guarded script, --ignore-scripts is an option for the runtime stage, as long as no other dependency needs its install scripts.

FROM node:20 AS build
WORKDIR /app
COPY package*.json ./
ENV HUSKY=0
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-slim AS runtime
WORKDIR /app
COPY package*.json ./
ENV HUSKY=0
RUN npm ci --omit=dev          # guarded prepare: no failure
COPY --from=build /app/dist ./dist
CMD ["node", "dist/server.js"]

.git is usually excluded from the build context, so Husky would skip anyway β€” but only if it is installed. The guard and HUSKY=0 handle both stages explicitly.

Step 5 β€” Keep Husky out of published packages Jump to heading

For libraries published to a registry, the prepare script runs before npm publish and when someone installs your package from a Git URL. Husky has no place in either. Move it to a script that only runs in development.

{
  "scripts": {
    "prepare": "husky || true",
    "prepublishOnly": "HUSKY=0 npm run build"
  },
  "files": ["dist/"]
}

Because files limits the published contents to dist/, the .husky/ directory never ships. Check what would be published before releasing:

npm pack --dry-run 2>&1 | grep -E '\.husky|prepare' || echo "no hook files in the package"

Step 6 β€” Verify every environment once Jump to heading

After changing the setup, run each install path once so you know it behaves.

HUSKY=0 npm ci && git config --get core.hooksPath || echo "CI: hooks not configured (expected)"
rm -rf node_modules && npm ci --omit=dev && echo "production install: ok"
docker build -t app-test . && echo "docker: ok"
Which fix does this environment need?A production install that omits dev dependencies needs a guarded prepare script. A CI pipeline needs HUSKY set to zero so hooks neither install nor run. A published library needs Husky kept out of its packaged files and publish scripts.Where is Husky misbehaving?npm ci --omit=devGuard preparehusky || trueCI pipelineHUSKY=0env for the whole jobpublished packagefiles + scriptskeep .husky outmost projects need the first two; libraries need all three

Validation checklist Jump to heading

Frequently Asked Questions Jump to heading

Is --ignore-scripts a better fix than guarding prepare? Jump to heading

It skips every lifecycle script, including those that dependencies need to compile native modules. Use it only where you know no dependency relies on install scripts; the guarded prepare is safer as a default.

Will HUSKY=0 in CI skip checks we rely on? Jump to heading

No β€” it skips local hooks, which CI should not rely on. Every check a hook runs should also be a CI step, which is where enforcement belongs.

What about pnpm and Yarn? Jump to heading

They run prepare similarly, with some differences in workspaces and Yarn’s Plug’n’Play mode. The specifics are in using Husky with pnpm and Yarn.