Git Deployment and You: Static Site Edition
This is how I am currently deploying the build of my static website to staging and production domains. It requires basic use of the CLI, Git, and SSH. But once you’re set up, a single command will build and deploy.
What you Will Need:
- A remote web server to host your site.
- SSH access to your remote server.
- Git installed on your remote server (check with
git --version
). - Generate an SSH key if you need one.
Once on the Server
Set up password-less SSH access
First, you need to SSH into your server, and provide the password if prompted.
ssh user@hostname
If there is no ~/.ssh
directory in your user’s home directory, create one: mkdir ~/.ssh
.
Next, you need to copy your public SSH key (see “Generate an SSH key” above) to the server. This allows you to connect via SSH without having to enter a password each time.
From your local machine – assuming your public key can be found at ~/.ssh/id_rsa.pub
– enter the following command, with the correct user
and hostname
. It will append your public key to the authorized_keys
file on the remote server.
ssh user@hostname 'cat >> ~/.ssh/authorized_keys' < ~/.ssh/id_rsa.pub
If you close the connection, and then attempt to establish SSH access, you should no longer be prompted for a password.
Now Create the Remote Directories
You need to have 2 directories for each domain you want to host. One for the Git repository, and one to contain the checked out build.
For example, if your domain were somesite.com
and you also wanted a staging environment, you’d create these directories on the server:
mkdir ~/somesite.com ~/somesite.git
mkdir ~/staging.somesite.com ~/staging.somesite.git
Then You Can Initialize the Bare Git Repository
Create a bare Git repository on the server. This is where you will push the build assets to, from your local machine. But you don’t want the files served here, which is why it’s a bare repository.
cd ~/somesite.git
git init --bare
Repeat this step for the staging domain, if you want.
Write a Post-Receive Hook
A post-receive hook allows you to run commands after the Git repository has received commits. In this case, you can use it to change Git’s working directory from somesite.git
to somesite.com
, and check out a copy of the build into the somesite.com
directory.
The location of the working directory can be set on a per-command basis using GIT_WORK_TREE
, one of Git’s environment variables, or the --work-tree
option.
cat > hooks/post-receive
#!/bin/sh
WEB_DIR=/path/to/somesite.com
# remove any untracked files and directories
git --work-tree=${WEB_DIR} clean -fd
# force checkout of the latest deploy
git --work-tree=${WEB_DIR} checkout --force
Make sure the file permissions on the hook are correct.
chmod +x hooks/post-receive
If you need to exclude some files from being cleaned out by Git (e.g., a .htpasswd
file), you can do that using the --exclude
option. This requires Git 1.7.3 or above to be installed on your server.
git --work-tree=${WEB_DIR} clean -fd --exclude=<pattern>
Repeat this step for the staging domain, if you want.
On your local machine
Now that the server configuration is complete, you want to deploy the build assets (not the source code) for the static site.
The build and deploy tasks
I’m using a Makefile, but use whatever you feel comfortable with. What follows is the basic workflow I wanted to automate.
-
Build the production version of the static site.
make build
-
Initialize a new Git repo in the build directory. I don’t want to try and merge the new build into previous deploys, especially for the staging domain.
git init ./build
-
Add the remote to use for the deploy.
cd ./build git remote add origin ssh://user@hostname/~/somesite.git
-
Commit everything in the build repo.
cd ./build git add -A git commit -m "Release"
-
Force-replace the remote
master
branch, creating it if missing.cd ./build git push -f origin +master:refs/heads/master
-
Tag the checked-out commit SHA in the source repo, so I can see which SHA’s were last deployed.
git tag -f production
Using a Makefile:
BUILD_DIR := ./build
STAGING_REPO = ssh://user@hostname/~/staging.somesite.git
PROD_REPO = ssh://user@hostname/~/somesite.git
install:
npm install
\# Deploy tasks
staging: build git-staging deploy
@ git tag -f staging
@ echo "Staging deploy complete"
prod: build git-prod deploy
@ git tag -f production
@ echo "Production deploy complete"
\# Build tasks
build: clean
\# whatever your build step is
\# Sub-tasks
clean:
@ rm -rf $(BUILD_DIR)
git-prod:
@ cd $(BUILD_DIR) && git init && git remote add origin $(PROD_REPO)
git-staging:
@ cd $(BUILD_DIR) && git init && git remote add origin $(STAGING_REPO)
deploy:
@ cd $(BUILD_DIR) && git add -A && git commit -m "Release" && git push -f origin +master:refs/heads/master
.PHONY: install build clean deploy git-prod git-staging prod staging
To deploy to staging:
make staging
To deploy to production:
make prod
Using Make, it’s a little bit more hairy than usual to force push to master
, because the cd
commands take place in a sub-process. You have to make sure subsequent commands are on the same line. For example, the deploy
task would force push to your source code’s remote master branch if you failed to join the commands with &&
or ;
!