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@hostnameIf 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.pubIf 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.gitThen 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 --bareRepeat 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 --forceMake sure the file permissions on the hook are correct.
chmod +x hooks/post-receiveIf 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
masterbranch, 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 stagingTo deploy to staging:
make stagingTo deploy to production:
make prodUsing 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 ;!