Deploying a static website using SFTP and a CI server

Automating the deployment of a static website makes updating it much more enjoyable. This blog is hosted on a basic plan which offers only FTP and SFTP access. Here’s how we deploy it in a matter of seconds using our CI server, with the security of SSH keys and lftp instead of rsync.

To avoid using/storing a password on the CI server, we wanted to use an SSH key for authentication. It had to be used by lftp, a command line SFTP client whose mirror command works a lot like rsync.

Using SSH keys on CI and deployment servers

First you have to generate a public and private key using ssh-keygen. Your CI server needs an SSH private key without a passphrase to be able to connect to your deployment server without any user input. So, run:

ssh-keygen -f id_rsa_deploy

You will get an output similar to this one:

Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in id_rsa_deploy.
Your public key has been saved in id_rsa_deploy.pub.
The key fingerprint is:
SHA256:bNN57ngNVryt8gkSQMHvuZb6K4jLvN/mzAV210RHbYs you@machine.local
The key's randomart image is:
+---[RSA 2048]----+
|       .o.    ..+|
|       ..    . .o|
|        ..   .o..|
|       . o.. Eo. |
|        S.=.o..o |
|       o +o=o . .|
|     . .  o+oo . |
|   o. .+o.++o.o. |
|    =+.o*=+o.oo  |
+----[SHA256]-----+

The files will be named id_rsa_deploy for the private key and id_rsa_deploy.pub for the public key.

How to store a private SSH key on a CI system varies. Semaphore CI — the one we use currently — has an option in each deployment server’s settings to store SSH private keys. The private key we generated and uploaded during the setup process did not work, so we added another one that will be accessible on the CI server at /home/user/.ssh/id_rsa_deploy.

Don’t forget to encrypt the key after it has been uploaded to your CI service. You don’t want someone accessing your CI account and copying the private key to connect to your production server.

On the deployment server, add the public key to ~/.ssh/authorized_keys either by copying and pasting, or by using ssh-copy-id:

ssh-copy-id -i generated_key.pub user@your.host

Skipping SSH’s host key checking

When you first connect to a server using SSH, it asks if you want to continue connecting to a host not precedently known:

$ ssh user@host.com
The authenticity of host 'host.com (8.8.8.8)' can't be established.
ECDSA key fingerprint is SHA256:djvayJCE2lo9ZrPR5uIwc9osOjfRa1BqW8hoYCY1dv4.
Are you sure you want to continue connecting (yes/no)?

Because we deploy in a CI environment, automatically, we cannot type “yes” to confirm. We have to skip this message using this SSH option:

StrictHostKeyChecking=no

Also, to avoid uncessary verbosity during deployment, we can skip adding the key fingerprint to the known hosts list by specifying:

UserKnownHostsFile=/dev/null

Both commands can be passed to ssh using the -o flag:

ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no

To tell lftp to use SSH to connect, we have to use this command before the upload/sync command:

set sftp:connect-program "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i /home/user/.ssh/id_rsa_deploy";

Syncing files with lftp over SFTP

The final steps toward a complete sync command are the connect and mirroring commands passed to lftp.

Let’s say our built website is stored in ~/build on the CI server and we want to deploy to ~/www on the production server. The command will be:

lftp -c "set sftp:connect-program 'ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i /home/user/.ssh/id_rsa_deploy'; connect sftp://user:@your.host; mirror -ceRL build www"

Note one very important thing for the connect command: you have to specify an empty password so lftp can use the SSH key to authenticate itself. If you specify a passwordless URL like sftp://user:@your.host you will get a password prompt from lftp.

Syncing files with rsync over SSH

If we could have used a SSH connexion, we would be using rsync with this command:

rsync -e "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i /home/user/.ssh/id_rsa_deploy" --links --times --delete --recursive --safe-links build/ user@your.host:~/www/
  • --links copies symlinks as symlinks
  • --times preserves modification times
  • --delete deletes remote files that are present in the source
  • --recursive copies all files recursively
  • --safe-links ignores symlinks that point outside the tree