Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Simple automated GIT Deployment using Hooks

Simple automated GIT Deployment using GIT Hooks

Here are the simple steps needed to create a deployment from your local GIT repository to a server based on this in-depth tutorial.

How it works

You are developing in a working-copy on your local machine, lets say on the master branch. Most of the time, people would push code to a remote server like github.com or gitlab.com and pull or export it to a production server. Or you use a service like deepl.io to act upon a Web-Hook that's triggered that service.

But here, we add a "bare" git repository that we create on the production server and pusblish our branch (f.e. master) directly to that server. This repository acts upon the push event using a 'git-hook' to move the files into a deployment directory on your server. No need for a midle man.

This creates a scenario where there is no middle man, high security with encrypted communication (using ssh keys, only authorized people get access to the server) and high flexibility tue to the use of .sh scripts for the deployment.

Prerequisit

  1. Know how to use GIT, Terminal etc.
  2. Have a local working-working copy ready
  3. Have SSH access to your server using private/public key

Todos

  1. Create a folder to deploy to on production server (i.e. your httpds folder)
  2. Add a bare repository on the productions server
  3. Add the post-receive hook script to the bare repository (and make it executable)
  4. Add the remote-repository resided on the production server to your local repository
  5. Push to the production server, relax.

1. Have a local working-working copy ready

Nuf said. I asume we are working on master – but you could work on any branch.

2. Create a folder to deploy to

ssh into your prodctionserver:

$ ssh user@server.com
$ mkdir ~/deploy-folder

3. Add a bare repository on the productions server

Now we create a "bare" repository – one that does not contain the working copy files. It basicaly is the content of the .git repository folder in a normal working copy. Name it whatever you like, you can also ommit the .git part from project.git or leave it to create the repository in an exisiting empty folder:

$ git init --bare ~/project.git

4. Add the post-receive hook script

This scrtipt is executed when the push from the local machine has been completed and moves the files into place. It recides in project.git/hooks/ and is named 'post-receive'. You can use vim to edit and create it. The script does check if the correct branch is pushed (not deploying a develop branch for example). You can download a sample post-receive script below. Also, don't forget to add execute permissions to said script;

chmod +x post-receive

5. Add remote-repository localy

Now we add the this bare repository to your local system as a remote. Where "production" is the name you want to give the remote. This also be called "staging" or "live" or "test" etc if you want to deploy to a different system or multiple systems.

$ cd ~/path/to/working-copy/
$ git remote add production demo@yourserver.com:project.git

Make sure "project.git" coresponds to the name you gave in step 3. If you are using Tower or a similar App, you will see the newly added remote in your sidebar under "Remotes" (make sure it's not collapsed).

6. Push to the production server

Now you can push the master branch to the production server:

$ git push production master

If you are using tower, you can drag&drop the master branch onto the new production remote. That's it. Have questions, improvements?

#!/bin/bash
TARGET="/home/webuser/deploy-folder"
GIT_DIR="/home/webuser/www.git"
BRANCH="master"
while read oldrev newrev ref
do
# only checking out the master (or whatever branch you would like to deploy)
if [ "$ref" = "refs/heads/$BRANCH" ];
then
echo "Ref $ref received. Deploying ${BRANCH} branch to production..."
git --work-tree=$TARGET --git-dir=$GIT_DIR checkout -f $BRANCH
else
echo "Ref $ref received. Doing nothing: only the ${BRANCH} branch may be deployed on this server."
fi
done
@imprfekt
Copy link

imprfekt commented Mar 26, 2020

@sm30482 The answer you're looking for lies in the question you ask.

@MarioVilas
Copy link

MarioVilas commented Mar 26, 2020

@precompiled: That was both pedantic and unhelpful, because git always gives that error when something goes wrong during a push.

@sm30482: I'd check if ssh is working properly and examine the logs at the server.

@imprfekt
Copy link

imprfekt commented Mar 26, 2020

@MarioVilas Uh, you make me look like a bad guy here.

I don't think it's unhelpful to ask @sm30482 to look at the self-explanatory error message he/she is receiving.

What I'm proposing for everyone getting an error and trying to find a solution (although that might not be obvious from my initial message) is:

  1. Read the error message, it contains key information.
  2. Consult the documentation and Internet (existing resources).
    (If you have invested more than X minutes of your time for the 1. and 2. and didn't can't find the solution), then:
  3. Ask others for help with as much context and relevant details as possible (without obfuscating the problem)

I believe it's very helpful to promote problem-solving attitude and self-reliance in engineering.

No hard feelings! Thanks!

@MarioVilas
Copy link

MarioVilas commented Mar 27, 2020

@precompiled so nice of you to not call into question the "pedantic" bit and in fact doubling down on it 👍

@imprfekt
Copy link

imprfekt commented Mar 27, 2020

@MarioVilas Good, now both of us can agree that my comment was both pedantic and helpful. :)

@shakibamoshiri
Copy link

shakibamoshiri commented Apr 2, 2020

Thank you because of very simple and easy automation approach, but for real world we need more to setup and implement.

Also since GIT_DIR is set by default you no need to use --git-dir option since on a --bare repository it is set by default so

git --work-tree=/var/www/html/version-control/dr checkout -f

will be enough. but @hous04 asked a good question. How to sync Deployed Version with Local Version?
The real workflow is that we almost NEVER should directly push to deployed version. This way bad because if someone else clones from --bare repository and does some changes, she/he can push to --bare repository which automatically working tree is send to deployed version.

2020-04-02-231852_1600x900_scrot

Github and others do not work that way.

The deployed version just should pull from --bare repository if / and when needed. On github/gitlab you do a push request which tell the deployed version: hey, could you accept my changes? , and a person responsible for deployed version does a git fetch and will take look at the changes and if liked, then does a merge or rejected.

This solution cannot be used in this way because it does not have .git at all, it is NOT a repository at all. So synchronizing back from deployed version to local version is not possible, while in github, etc is possible.

The solution is still using hooks and not for pushing to deployment server, but for notifying it to do a pull if needed.
here is my approach, private-git-server

@RidenShark
Copy link

RidenShark commented Jul 23, 2020

I did all this. And the push works too. The code gets updated into the project. But when I request my site the apache error log shows fatal: not a git repository (or any of the parent directories): .git .

@MidoAhmed
Copy link

MidoAhmed commented Oct 19, 2020

Hello, i'm using travis and it can't push to a bare repo that i have created on a server ! any solution :)

@joelhoelting
Copy link

joelhoelting commented Oct 24, 2020

TARGET="/home/webuser/deploy-folder"
GIT_DIR="/home/webuser/www.git"
BRANCH="master"

while read oldrev newrev ref
do
	# only checking out the master (or whatever branch you would like to deploy)
	if [ "$ref" = "refs/heads/$BRANCH" ];
	then
		echo "Ref $ref received. Deploying ${BRANCH} branch to production..."
		git --work-tree=$TARGET --git-dir=$GIT_DIR checkout -f $BRANCH
	else
		echo "Ref $ref received. Doing nothing: only the ${BRANCH} branch may be deployed on this server."
	fi

I followed all the steps but I cannot figure out one thing. What does $GIT_DIR refer to? Is that supposed to be the bare repository or something else?

@MidoAhmed
Copy link

MidoAhmed commented Oct 25, 2020

@joelhoelting, yeah it's a bare repo

@GabrieleCalarota
Copy link

GabrieleCalarota commented Oct 28, 2020

Thanks a lot @noelboss It helped me a lot without having to set callbacks, hooks or any PHP script!

@michaellee1
Copy link

michaellee1 commented Nov 23, 2020

What happens in the case of two simultaneous deploys? Would there be a conflict / problem?

@patricktan98
Copy link

patricktan98 commented Nov 27, 2020

If the repo should sync from GitHub repository what should I replace?

@zedxos
Copy link

zedxos commented Dec 17, 2020

Arigato gozaimasu

Konnichiwa Onee Chan

@terrylinooo
Copy link

terrylinooo commented Jan 20, 2021

@altcointrading
Copy link

altcointrading commented Feb 13, 2021

What does this means?

remote: error: refusing to update checked out branch: refs/heads/master
remote: error: By default, updating the current branch in a non-bare repository
remote: is denied, because it will make the index and work tree inconsistent
remote: with what you pushed, and will require 'git reset --hard' to match
remote: the work tree to HEAD.
remote:
remote: You can set 'receive.denyCurrentBranch' configuration variable to
remote: 'ignore' or 'warn' in the remote repository to allow pushing into
remote: its current branch; however, this is not recommended unless you
remote: arranged to update its work tree to match what you pushed in some
remote: other way.
remote:
remote: To squelch this message and still keep the default behaviour, set
remote: 'receive.denyCurrentBranch' configuration variable to 'refuse'.
To smf:atgp-bare
 ! [remote rejected] master -> master (branch is currently checked out)

also, how is this better than using rsync?

@shakibamoshiri
Copy link

shakibamoshiri commented Feb 18, 2021

@altcointrading
It seems you are pushing directly to a working directory
You can push to bare repository and using hooks notify working directory to do a git pull ... request
You can set it with post-receive hook e.g. using curl to send a POST request to somewhere else , and there the receiver of POST request can do a git pull ... to synchronize itself with bare repository .

20210218_14o2206

@altcointrading
Copy link

altcointrading commented Feb 18, 2021

@k-five thanks for responding. I tried once again to follow step by step the guide and there must be a step missing.

Enumerating objects: 868, done.
Counting objects: 100% (868/868), d
...
Total 868 (delta 474), reused 0 (delta 0)
remote: Resolving deltas: 100% (474/474), done.
remote: Ref refs/heads/master received. Deploying master branch to production...
remote: fatal: Not a git repository: '/home/myuser/www.git'
To myserver:project.git
 * [new branch]      master -> master

Did I understand the sequence right?

  • I put the post-receive hook (777) in the bare repo (~/project.git).
  • In the post-receive script I only change my username.
  • I add the "production" remote to the repo on my local machine. This "production" remote points to the bare repo (~/project.git)
  • What is the www.git repo? I understand it's defined the the post-receive hook for a reason I do not understand. What is it and what is it for?

Thanks again

@shakibamoshiri
Copy link

shakibamoshiri commented Feb 19, 2021

@altcointrading

It is super simple, you can follow bellow setps

> cd /tmp
> # create two dirs 
> mkdir repo{,.git}
> cd repo
> git init
> git remove add server /tmp/repo.git
> # go back to repo.git
> cd ../repo.git
> git init --bare
> cd hooks 
> # remove all scripts 
> rm -fr *
> # create update script
> vim update 
> # add a shell script e.g.
#!/bin/bash
echo "this is update file"

> # create new one
> vim post-receive 
> # add shell script 
#/bin/bash
echo "this is post-receive file"

> # add exec permission to them
> chmod u+x *
> # go back to /tmp/repo
> cd /tmp/repo
> echo "this is line 1' >> file
> git add file
> git commit -m 'Add new file';
> git push -u server master

and you will have output like this

Counting objects: 3, done.
Writing objects: 100% (3/3), 227 bytes | 227.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
remote: this is update in hooks
remote: this is post-receive in hooks
To /tmp/repo.git
 * [new branch]      master -> master
Branch 'master' set up to track remote branch 'master' from 'server'.

20210219_16o2114

remote: Resolving deltas: 100% (474/474), done.
remote: Ref refs/heads/master received. Deploying master branch to production...
remote: fatal: Not a git repository: '/home/myuser/www.git'

this part is coming back from bare repository which is a kind of server here it prints into stdout or stderr which is received by git client, the one you used to do git push ...

and inside hooks

20210219_16o2941

@zedxos
Copy link

zedxos commented Feb 23, 2021

@altcointrading

It is super simple, you can follow bellow setps

> cd /tmp
> # create two dirs 
> mkdir repo{,.git}
> cd repo
> git init
> git remove add server /tmp/repo.git
> # go back to repo.git
> cd ../repo.git
> git init --bare
> cd hooks 
> # remove all scripts 
> rm -fr *
> # create update script
> vim update 
> # add a shell script e.g.
#!/bin/bash
echo "this is update file"

> # create new one
> vim post-receive 
> # add shell script 
#/bin/bash
echo "this is post-receive file"

> # add exec permission to them
> chmod u+x *
> # go back to /tmp/repo
> cd /tmp/repo
> echo "this is line 1' >> file
> git add file
> git commit -m 'Add new file';
> git push -u server master

and you will have output like this

Counting objects: 3, done.
Writing objects: 100% (3/3), 227 bytes | 227.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
remote: this is update in hooks
remote: this is post-receive in hooks
To /tmp/repo.git
 * [new branch]      master -> master
Branch 'master' set up to track remote branch 'master' from 'server'.

20210219_16o2114

remote: Resolving deltas: 100% (474/474), done.
remote: Ref refs/heads/master received. Deploying master branch to production...
remote: fatal: Not a git repository: '/home/myuser/www.git'

this part is coming back from bare repository which is a kind of server here it prints into stdout or stderr which is received by git client, the one you used to do git push ...

and inside hooks

20210219_16o2941

Hmmm ty

@Dimitri-WEI-Lingfeng
Copy link

Dimitri-WEI-Lingfeng commented Mar 11, 2021

very useful and simple! thank you!

@Devoleksiy
Copy link

Devoleksiy commented Apr 13, 2021

I like the implementation.
What command can I add files from the working directory, for example, the working folder was not empty.
Any advice, I Might be able to move on.

@Hawzen
Copy link

Hawzen commented May 10, 2021

Life saver

@junglistcode
Copy link

junglistcode commented May 19, 2021

so if the server disappears or you delete it, you lose your git repository...

what about staging or test environment? different git repository? this makes no sense

But you'd just lose the remote right? You still have your local on your develepment machine won't u?

@norley-tucker
Copy link

norley-tucker commented Dec 27, 2021

Thanks very much, very helpful!

@shakibamoshiri
Copy link

shakibamoshiri commented Mar 21, 2022

win-20220321_18o0859

here is my new approach with these steps

  • setting up a git server
  • create a bare repo on git server - git init --bare
  • setting up post-update on bare repo
  • setting up a working tree which pulls form bare repo (git init)
  • pushing from working tree to another server after some tests with port-merge

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment