Running pre-commit hooks on postCreateCommand with Husky

Husky installs Git hooks by rewriting core.hooksPath, but that only happens when someone runs npm install with the right lifecycle — and in a DevContainer the workspace is mounted after the image build, so hooks are frequently inactive until a developer notices. This page wires Husky activation into postCreateCommand so every container has working commit hooks immediately. It extends the Pre-commit Hook Configuration for Containerized Workflows cluster.

Prerequisites

  • Node.js 20+ in the container and Husky 9+ in devDependencies
  • Git 2.36+ (for core.hooksPath support)
  • The repository mounted at containerWorkspaceFolder

How-To Steps

  1. Add the Husky install to postCreateCommand so hooks activate after the workspace mounts:

    {
      "image": "mcr.microsoft.com/devcontainers/javascript-node:20@sha256:7c3e...",
      "postCreateCommand": "npm ci && npx husky",
      "remoteUser": "vscode"
    }
    

    Verify: git config core.hooksPath prints .husky/_.

  2. Define a pre-commit hook at .husky/pre-commit:

    #!/usr/bin/env sh
    npx lint-staged
    
    chmod +x .husky/pre-commit
    
  3. Mark the workspace safe for Git so hooks run inside the container without “dubious ownership” errors:

    { "postStartCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}" }
    
  4. Verify the hook fires with a throwaway commit:

    echo "x" > scratch.txt && git add scratch.txt
    git commit -m "test" --dry-run   # lint-staged runs; abort if it reports issues
    

Common Pitfalls

SymptomRoot CauseRemediation
Hooks never runhusky activated during image build, before workspace mountRun npx husky in postCreateCommand
dubious ownership in repositoryWorkspace UID differs from container userAdd safe.directory in postStartCommand
Hook runs but skips fileslint-staged config missing or wrong globsAdd a lint-staged block to package.json
permission denied on hookHook file not executablechmod +x .husky/*

Conclusion

The invariant: Husky must be activated after the workspace is mounted, which means postCreateCommand, never the image build. Pair that with a safe.directory entry and your commit hooks work from the first commit in every freshly created container.

FAQ

Why not activate Husky in the Dockerfile? The Dockerfile builds before the repository is mounted, so .husky/ doesn’t exist yet and core.hooksPath points nowhere. Activation belongs in postCreateCommand.

Does this work with pnpm or Yarn? Yes — replace npm ci && npx husky with the equivalent (pnpm install && pnpm exec husky or yarn install && yarn husky). The activation step is package-manager agnostic.