Zola Automated Deploy To Digitalocean
I wrote a few scripts to automate deploying Zola to Digitalocean VPS. I have an existing account with Digitalocean so I just went with them. This assumes you have already configured your domain to use Digialocean's nameservers as it will be needed when we register and obtain our SSL certificate from Lets Encrypt. Although, we will only provision our server once, I decided to just complete the flow from provisioning and deploying of our Zola blog.
We will use Ansible to automate our remote commands to
the server. Check with your desktop operating system package manager on how to
install Ansible
. On systems I'm familiar with:
MacOS
brew install ansible
Debian/Ubuntu
apt-get install ansible
Fedora
dnf install ansible
Or better yet, consult the ansible documentation for detail instructions.
Let's start by creating an inventory file containing only the IP address of our VPS in the Zola root directory. Ansible will query this file for list of IP address to login into. The IP address can be found in our digitalocean project page, beside the droplet name.
# ./inventory
[webservers]
123.210.123.210
The ansible playbook (set of tasks in yaml format) below will install and configure Nginx. It will also install Certbot and its utilities to register/obtain SSL certificate and configure Nginx to use it. Additionally, it will create a user that we will use when uploading our blog content. We will be uploading our SSH public key so we will not be providing a username and password everytime we upload content later on. This user will have write permission our custom Nginx document root.
# ./provision.yml
---
- name: Provision server
hosts: webservers
become: yes
vars:
domains:
- "atske.com"
tasks:
- name: Update apt cache and install nginx
apt:
name: nginx
state: latest
update_cache: yes
- name: Install certbot
apt:
name: certbot
state: latest
update_cache: yes
- name: Install python3-certbot-nginx
apt:
name: python3-certbot-nginx
state: latest
update_cache: yes
- name: Create user
user:
name: atske
groups: www-data
shell: /bin/bash
- name: Upload authorized key
authorized_key:
user: kates
state: present
key: "{{ lookup('file', '~/.ssh/do_ed25519.pub') }}"
- name: Create document root
file:
path: "/var/www/{{ item }}/public"
state: directory
with_items: "{{ domains }}"
- name: Set permissions for /var/www
file:
path: "/var/www"
mode: "666"
group: "www-data"
- name: Create temporary index.html
shell: |
cat > /var/www/{{ item }}/public/index.html <<EOF
<html>
<head><title>{{ item }}</title></head>
<body>
<h1>{{ item }}</h1>
</body>
</html>
EOF
with_items: "{{ domains }}"
- name: Create website confs
shell: |
cat > /etc/nginx/sites-available/{{ item }} <<EOF
server {
listen 80;
listen [::]:80;
server_name {{ item }} www.{{ item }};
root /var/www/{{ item }}/public;
index index.html;
location / {
try_files \$uri \$uri/ =404;
}
}
EOF
cat > /etc/nginx/sites-available/https-{{ item }} <<EOF
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name {{ item }} www.{{ item }};
root /var/www/{{ item }}/public;
index index.html;
location / {
try_files \$uri \$uri/ =404;
}
ssl_certificate /etc/letsencrypt/live/{{ item }}/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/{{ item }}/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
EOF
with_items: "{{ domains }}"
- name: Enable websites
file:
src: /etc/nginx/sites-available/{{ item }}
dest: /etc/nginx/sites-enabled/{{ item }}
state: link
with_items: "{{ domains }}"
- name: Restart Nginx
systemd_service:
name: nginx
state: restarted
- name: Obtain letsencrypt certificate
shell: |
certbot register --nginx --agree-tos --email myemail@gmail.com -d {{ item }} -d www.{{ item }}
touch /etc/letsencrypt/.registered-{{ item }}
args:
creates: /etc/letsencrypt/.registered-{{ item }}
tags:
- nginx
- certbot
with_items: "{{ domains }}"
- name: Restart Nginx
systemd_service:
name: nginx
state: restarted
- name: Enable https websites
file:
src: /etc/nginx/sites-available/https-{{ item }}
dest: /etc/nginx/sites-enabled/https-{{ item }}
state: link
with_items: "{{ domains }}"
- name: Restart Nginx
systemd_service:
name: nginx
state: restarted
To provision our droplet, we will run the playbook with
ansible-playbook -i inventory provision.yml -u root
We only need to do this once.
Next is the ansible playbook that we will be using everytime we need to upload
our blog content to our Digitalocean VPS. It is basically just a series of shell
command to run build Zola and compress the output. It will the upload the
generated file to our user's home directory. Delete the Nginx document root, then
unpack the upload compressed file in the place of the old document root. Finally,
make sure that the unpacked files are readable by Nginx by issuing the chown
command.
# upload.yml
---
- name: Builder
hosts: localhost
tasks:
- name: Build zola
shell: zola build
- name: Compress
shell: tar -czf site.tar.gz public
- name: Uploader
hosts: webservers
remote_user: atske
vars:
domain: atske.com
tasks:
- name: Upload file
copy:
src: site.tar.gz
dest: ~/site.tar.gz
mode: "666"
- name: Unpack and cleanup
shell: |
rm -rf /var/www/{{ domain }}/public
tar -xzf site.tar.gz -C /var/www/{{ domain }}
chown -R "$USER":www-data /var/www/{{ domain }}
rm ~/site.tar.gz
- name: Cleanup local
hosts: localhost
tasks:
- name: Cleanup
shell: |
rm site.tar.gz
rm -rf public
Upload the whole zola project by running
ansible-playbook -i inventory upload.yml --verbose
Reason why we upload the whole zola project and not only content
folder is that
it's a lot simpler process. By doing so, we have a way of having our blog
configuration changes reflected when we subsequently deploy.
Hope this will make blogging with Zola easier and more fun.