Git hooks are scripts that run automatically at specific points in the Git workflow — before a commit, after a push, before a merge — useful for enforcing standards or triggering simple automation without a full CI/CD pipeline.
Client-Side vs Server-Side Hooks
| Type | Runs On | Common Use |
|---|---|---|
| Client-side (pre-commit, pre-push) | Developer's local machine | Linting, running tests before allowing a commit/push |
| Server-side (post-receive) | The Git server itself | Triggering deployment when code is pushed |
Client-Side Example: pre-commit Hook
cd your-repo
nano .git/hooks/pre-commit
#!/bin/bash
npm run lint
if [ $? -ne 0 ]; then
echo "Linting failed. Commit aborted."
exit 1
fi
chmod +x .git/hooks/pre-commit
Now every git commit automatically runs the linter first, blocking the commit if it fails.
Limitation: Hooks in .git/hooks Aren't Version Controlled
Since .git/hooks isn't tracked by Git itself, these hooks don't automatically propagate to other developers cloning the repository. Solutions:
- Use a dedicated tool like Husky (for Node.js projects) that manages hooks as part of the repository
- Store hook scripts in a tracked directory and document a one-time setup step to symlink them
Example with Husky (Node.js Projects)
npm install husky --save-dev
npx husky init
echo "npm test" > .husky/pre-commit
Husky's configuration lives in a tracked directory, so it works automatically for every team member after npm install.
Server-Side Example: post-receive Hook for Deployment
On a self-hosted Git server (bare repository) on your VPS:
cd /var/git/myapp.git
nano hooks/post-receive
#!/bin/bash
GIT_WORK_TREE=/var/www/myapp git checkout -f main
cd /var/www/myapp
npm install --production
pm2 reload myapp
chmod +x hooks/post-receive
Now pushing to this bare repository automatically deploys the latest code — a simple self-hosted alternative to webhook-based deployment (see How to Deploy Automatically on Git Push) when you're hosting your own Git repository directly, without a platform like GitHub/GitLab in between.
Setting Up a Bare Repository for This Pattern
sudo mkdir -p /var/git/myapp.git
cd /var/git/myapp.git
sudo git init --bare
Add it as a remote from your local machine:
git remote add production deploy@YOUR_SERVER_IP:/var/git/myapp.git
git push production main
Other Useful Hook Types
| Hook | Runs | Common Use |
|---|---|---|
| pre-push | Before pushing | Run full test suite before allowing push |
| commit-msg | After writing commit message | Enforce commit message format/conventions |
| post-checkout | After switching branches | Automatically install dependencies if they changed |
Common Errors
Hook doesn't run — verify it's executable: chmod +x .git/hooks/pre-commit, and has no file extension (Git looks for exactly pre-commit, not pre-commit.sh).
post-receive deployment fails silently — add explicit logging to the hook script to capture output for debugging.
Best Practices
- Use a tool like Husky for team-shared client-side hooks, since raw
.git/hooksaren't version controlled - Keep hook scripts simple and fast — slow pre-commit hooks frustrate developers and get bypassed
- For anything beyond simple deployment, prefer a proper CI/CD pipeline over server-side hooks
Continue Reading
- How to Deploy Automatically on Git Push (Webhook-Based Deployment)
- How to Build a Simple CI/CD Pipeline with GitHub Actions
- How to Install Gitea (Lightweight Git Server)
Browse more articles in DevOps & CI/CD.
