Developers

Deploying a Website with Git: Pull, Push-to-Deploy and Safe Habits

Git makes deployments repeatable and reversible. Compare pulling on the server with push-to-deploy hooks and keep secrets and the .git folder safe.

By Zebra Hosting Cloud

Uploading files by FTP works until the day two people overwrite each other's changes or you cannot remember which version is live. Deploying with Git replaces that with a clear history: every release is a commit, and going back means checking out an earlier one. This article compares the common ways to deploy with Git and shows a push-to-deploy setup in full.

Three ways to deploy with Git

Option 1: pull on the server

The simplest approach is to clone the repository on the server and update it with git pull:

cd /var/www/mysite
git pull origin main

The server needs read access to the repository. Create an SSH key on the server and add the public key to the repository as a read-only deploy key, which can fetch code but cannot push. Do not put your personal key on the server. This method needs no special setup and works well for small sites. Its weakness is manual execution: someone has to log in and run it, and a failed pull can leave a half-updated working tree. Use git pull --ff-only so Git refuses to create merge commits on the server.

Option 2: push to a bare repository with a hook

With this setup you deploy by running git push from your own machine, and the server updates the live files by itself. On the server, create a bare repository (history only, no working files):

git init --bare /home/deploy/mysite.git

Then create the hook file /home/deploy/mysite.git/hooks/post-receive containing:

#!/bin/bash
set -e
TARGET=/var/www/mysite
GIT_DIR=/home/deploy/mysite.git
git --work-tree="$TARGET" --git-dir="$GIT_DIR" checkout -f main
# add build steps here, for example:
# cd "$TARGET" && composer install --no-dev --optimize-autoloader

Make it executable with chmod +x /home/deploy/mysite.git/hooks/post-receive, and make sure the deploy user can write to /var/www/mysite. On your computer, add the server as a remote and push:

git remote add production [email protected]:mysite.git
git push production main

Replace the address and names with your own. Each push runs the hook, which checks out the new files. The hook's output appears in your terminal, which makes failures visible immediately.

Option 3: CI/CD pipelines

Hosted services such as GitHub Actions or GitLab CI can test your code and deploy it when you merge to the main branch. A typical job connects to the server over SSH with a dedicated key stored in the service's secret storage, then runs a deploy script. The advantage is that tests run first and nobody deploys from a laptop. The cost is more moving parts. Whatever tool you use, the server-side script is still just the same few commands, so understanding the basic methods first pays off.

Keep secrets and generated files out of the repository

Never commit passwords, API keys or .env files. Once something is in Git history, deleting the file does not remove it, and rewriting history is awkward and may be pointless if the repository was already cloned or hosted elsewhere. Add such files to .gitignore, keep the real values on the server, and rotate anything that was committed by accident. Build output (node_modules, vendor) is usually better generated during deployment than stored in Git.

Do not serve your .git folder

If the web root itself is a Git working copy, the .git directory sits inside the public folder, and a visitor may be able to download your source history through the browser. Prevent this by pointing the web server's document root at a public subfolder, or by blocking dotfiles. In Nginx:

location ~ /\.(?!well-known) {
    deny all;
}

This denies access to hidden files while still allowing the .well-known path used for certificate validation. After deploying, try visiting https://example.com/.git/config; you should get an error, not file contents.

Rollbacks, releases and a checklist

Tag releases (git tag v1.4.0) so you know exactly what is live. To go back, check out the previous tag or revert the problem commit and deploy again. Remember that code is only half the story: database migrations and uploaded files do not roll back with git checkout, so plan migrations to be backward compatible where you can. If you need to avoid even a moment of broken state during an update, the release-folder-and-symlink pattern described in our zero-downtime article builds on the same Git workflow.

A short deployment checklist

  1. Back up the database before any release that includes migrations.
  2. Deploy from a tagged or at least a clean, reviewed commit.
  3. Run dependency install and build steps in a script, not by hand.
  4. Reload or restart the application and clear caches as needed.
  5. Check the site and the logs immediately afterwards.

Build steps on the server or elsewhere?

If your project needs compiling, such as a JavaScript bundle, you can build on the server inside the hook or build in CI and ship the finished output. Building on a small VPS can be slow and may use all its memory, which can slow the live site during a deployment. Building elsewhere gives you the same artefact every time and keeps compilers off the production machine. Whichever you pick, put the commands in a script stored in the repository, so the process is documented and repeatable, and run them with a non-root user. For help with environments around a deployment, see the knowledge base.

Git deployments on Zebra Hosting

Plesk supports pulling a site from a Git repository, as covered in our guide for developers, while a VPS lets you build any of the workflows above yourself. See the VPS plans.