I’m too cheap to pay for CI/CD for a small personal project like this one, so I decided to do it myself. The “production” server is in my home, not exposed to the internet, and the only users are my family members.
On the production server I created a bare repository.
mkdir myrepo.git
cd myrepo.git
git init --bare --shared
We don’t care about merge conflicts in a production deployment, so I set
git config receive.denyNonFastForwards false
On the development machine, I added this as a remote and tested pushing with
git remote add production myuser@myhost:myrepo.git
git push production master
I wanted my post-receive
hook to be version-controlled, but git doesn’t support tracking files which are actually inside the .git
directory. Git also doesn’t play well with symlinks, and it doesn’t preserve permissions when checking files out. This means scripts will need to be called with bash script-name
instead of ./script-name
.
I created a script in the root of the repository, under version control. I found some info here on how to detect the name of the branch that was pushed.
My script simply checks the files out into a temporary directory, checks that the branch that was pushed was indeed deploy/production
and, if so, it calls another script which handles building the new Docker images, running the tests, creating and starting the new containers.
#!/bin/bash
# post-receive.sh
set -euxo pipefail
WORKDIR=~/deploy_temp
ENV=.env.production
echo "Determining branch"
if ! [ -t 0 ]
then
read -a ref
fi
BRANCH=$(sed 's,refs/heads/,,' <<< "${ref[2]}")
GIT_WORK_TREE="$WORKDIR" git checkout -f "$BRANCH"
if [[ "$BRANCH" == "deploy/production" ]]
then
pushd "$WORKDIR"
bash ./build_and_run.sh "$ENV"
fi
To make this script run, I created a hook file in the production remote git repository, myrepo.git/hooks/post-receive
which simply puts Bash into strict mode and then calls the main script as root. (We need to be root, otherwise we will be denied access to the Docker socket.)
#!/bin/bash
set -euxo pipefail
sudo /bin/bash ../../deploy_temp/post-receive.sh
I made the hook file executable.
chmod +x myrepo.git/hooks/post-receive
Unfortunately, because of how the script works, it has to be present on the remote before the push, if it is to run after the push. This won’t be a problem usually, but when first setting up the hook or making changes to it, I have to copy it over manually before pushing. I do this with
scp post-receive.sh myuser@myhost:deploy_temp/post-receive.sh
However, because the meaty parts of the deployment are handled afterwards, in build_and_run.sh
, I can push changes to that file and they will take effect on that push, without needing to do any manual copying. Awesome!
After initially putting the hook in place, I can make a production deployment by running the following command from my development machine.
git push production master:deploy/production -f