This is how I am 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.
TL;DR: Push the static build to a remote, bare repository that has a detached working directory (on the same server). A post-receive hook checks out the files in the public directory.
- 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.
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.
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 example.com
and you also wanted a staging environment, you’d create these directories on the server:
mkdir ~/example.com ~/example.git
mkdir ~/staging.example.com ~/staging.example.git
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 ~/example.git
git init --bare
Repeat this step for the staging domain, if you want.
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 example.git
to example.com
, and check out a copy of the build into the example.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
WEB_DIR=/path/to/example.com
git --work-tree=${WEB_DIR} clean -fd
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.
Now that the server configuration is complete, you want to deploy the build assets (not the source code) for the static site.
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/~/example.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.example.git
PROD_REPO = ssh://user@hostname/~/example.git
install:
npm install
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: clean
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 ;
!
I push my site’s source code to a private repository on BitBucket. One of the nice things about BitBucket is that it gives you the option to prevent deletions or history re-writes of branches.
If you have any suggested improvements, let me know on Twitter.