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.hooksPathsupport) - The repository mounted at
containerWorkspaceFolder
How-To Steps
-
Add the Husky install to
postCreateCommandso 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.hooksPathprints.husky/_. -
Define a pre-commit hook at
.husky/pre-commit:#!/usr/bin/env sh npx lint-stagedchmod +x .husky/pre-commit -
Mark the workspace safe for Git so hooks run inside the container without “dubious ownership” errors:
{ "postStartCommand": "git config --global --add safe.directory ${containerWorkspaceFolder}" } -
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
| Symptom | Root Cause | Remediation |
|---|---|---|
| Hooks never run | husky activated during image build, before workspace mount | Run npx husky in postCreateCommand |
dubious ownership in repository | Workspace UID differs from container user | Add safe.directory in postStartCommand |
| Hook runs but skips files | lint-staged config missing or wrong globs | Add a lint-staged block to package.json |
permission denied on hook | Hook file not executable | chmod +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.
Related
- Pre-commit Hook Configuration for Containerized Workflows — the parent cluster.
- Configuring pre-commit in a multi-language repo — the Python
pre-commitframework alternative. - Integrating ESLint & Prettier in DevContainers — the linters these hooks invoke.