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 email@example.com 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
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 -i generated_key.pub firstname.lastname@example.org
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 email@example.com The authenticity of host 'host.com (126.96.36.199)' 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:
Also, to avoid uncessary verbosity during deployment, we can skip adding the key fingerprint to the known hosts list by specifying:
Both commands can be passed to ssh using the
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no
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
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
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/ firstname.lastname@example.org:~/www/
--linkscopies symlinks as symlinks
--timespreserves modification times
--deletedeletes remote files that are present in the source
--recursivecopies all files recursively
--safe-linksignores symlinks that point outside the tree