Deploy Code to VPS in 30 Seconds with Git Bare Repository

Git tutorial - IT technology blog
Git tutorial - IT technology blog

The Problem: The Nightmare of “Manual Uploads”

You’ve just finished fixing a “critical” bug at 2 AM. Instead of going to sleep, you have to open FileZilla, find the right folder to drag and drop files, or SSH into the server to type git pull and restart the service. These repetitive tasks are not only boring but also prone to errors like overwriting the wrong files or forgetting to restart the server.

Back when I was freelancing, setting up GitHub Actions or Jenkins for a simple landing page or a small Telegram bot often took more time than writing the code itself. I needed a solution: Push code and be done. That’s when I realized Git Push-to-Deploy with a Bare Repository was the perfect lifesaver.

Why Use a Bare Repository?

When you run git init on a regular project, Git creates a hidden .git folder alongside the Working Directory (where your editable code lives). However, Git prevents pushing directly into an active Working Directory to avoid data conflicts.

Bare Repository solves this problem by completely removing the Working Directory. It only acts as a “storage” for history and objects. We will use this repository as a transit station, then use a Hook to automatically push the code to the actual application directory.

Step 1: Setting up the Server Environment

Suppose you have an Ubuntu VPS and want to deploy a Node.js application at /var/www/my-app. First, create the application folder and the Repository folder.

# Create the application directory
sudo mkdir -p /var/www/my-app
sudo chown $USER:$USER /var/www/my-app

# Create the Bare Repository
mkdir -p ~/repos/my-app.git
cd ~/repos/my-app.git
git init --bare

After running the commands above, the my-app.git directory will only contain configuration files like hooks, info… You won’t see the source code here—don’t worry, that’s the nature of a Bare Repo.

Step 2: Activating the Post-receive Hook

Hooks are scripts that run automatically when Git events occur. The post-receive script will be triggered immediately after the server receives all data from your push command.

Create the script file:

nano ~/repos/my-app.git/hooks/post-receive

Paste the following script content:

#!/bin/bash

TARGET="/var/www/my-app"
GIT_DIR="$HOME/repos/my-app.git"

while read oldrev newrev ref
do
    # Only deploy when pushing to the main branch
    if [[ $ref =~ .*/main$ ]];
    then
        echo "Main branch received. Deploying..."
        git --work-tree=$TARGET --git-dir=$GIT_DIR checkout -f main
        
        # Perform additional tasks
        cd $TARGET
        npm install --production
        pm2 restart my-app || pm2 start app.js --name my-app
        
        echo "Deployment complete!"
    else
        echo "Branch $ref does not support auto-deployment."
    fi
done

Grant execution permissions so the script can run:

chmod +x ~/repos/my-app.git/hooks/post-receive

Step 3: Connecting from Your Local Machine

On your local machine, add a new “remote” pointing to the VPS address. This only needs to be done once per project.

# Replace 'user' and 'ip' with your VPS information
git remote add deploy [email protected]:/home/user/repos/my-app.git

Step 4: Enjoy the New Workflow

Now, whenever you update your code, just type:

git push deploy main

The entire process takes less than 5 seconds. The terminal will display logs from the server, letting you know exactly if npm install ran or if pm2 restarted. You save at least 15-20 minutes a day compared to the manual method.

Practical Tips and Considerations

Through many client projects, I’ve gathered 3 important notes to avoid “silly” mistakes:

  • Write Permissions: Your SSH user must have write access to /var/www/my-app. If you encounter a Permission denied error, double-check the chown command from Step 1.
  • Cleaning up junk files: The checkout -f command overwrites old files but doesn’t delete files you’ve removed in Git. To keep the server clean, add git --work-tree=$TARGET --git-dir=$GIT_DIR clean -fd to your hook script.
  • Security with SSH Keys: Use SSH Keys instead of typing a password every time you push. It’s both faster and much more secure.

Conclusion

This method is perfect for freelancers or small teams that need speed and simplicity. It doesn’t replace massive CI/CD systems like GitHub Actions for large projects, but it wins hands down in terms of simplicity and resource efficiency. Instead of spending an entire afternoon writing complex YAML files, you only need 5 minutes to free yourself from tedious tasks.

Share: