Deploy to production

We'll follow the steps in Reference > Production.

1․ Create an Ubuntu VPS in e.g. DigitalOcean. Give it at least 1GB of memory.

Create a Digital Ocean account if you don't have one already. Go to the create droplet page. Change the default droplet to something cheaper:

Screenshot of creating a droplet on Digital Ocean

Also be sure to select an SSH key:

Screenshot of creating a droplet on Digital Ocean

You may need to add a new SSH key to your Digital Ocean account if you haven't done so already.

Now you can create the droplet.

2․ (Optional) If this is an important application, you may want to set up a managed Postgres instance and edit config.edn to use that for XTDB's storage backend instead of the filesystem. With the default standalone topology, you'll need to handle backups yourself, and you can't use more than one server.

We'll skip this. If you're deploying a Real Application with Real Users, you should at least enable Digital Ocean's weekly filesystem backups when you create the droplet, and preferably use their managed Postgres offering.

3․ Edit config.edn and set :biff.tasks/server to the domain you'd like to use for your app. For now we'll assume you're using Also update :biff/base-url. If you use main instead of master as your default branch, update :biff.tasks/deploy-cmd.

I'll use for my domain. Replace that with whatever domain you're using:

;; config.edn
{:prod {...
        :biff/base-url ""
 :dev {...}
 :tasks {...
         :biff.tasks/server ""}}

4․ Set an A record on that points to your Ubuntu server.

As a prerequisite to this step, you'll need to register a domain somewhere. I use Namecheap. After you point the domain at Digital Ocean's name servers, you can add the domain to your DigitalOcean account. Then, create an A record for the domain (or a subdomain) and point it at the droplet you created earlier. I'm using the subdomain:

Screenshot of creating a DNS record on Digital Ocean

5․ Make sure you can ssh into the server, then run scp

Go ahead and run the command:

$ scp
The authenticity of host ' (' can't be established.
ECDSA key fingerprint is SHA256:BKnRyRjJlwsQTWi9ktWVz2gQz7sLBa2vuB4dNghFlGI.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added ',' (ECDSA) to the list of known hosts.                                    100% 3278    50.3KB/s   00:00

6․ Run ssh, then bash After it finishes, run reboot.

$ ssh
Welcome to Ubuntu 22.10 (GNU/Linux 5.19.0-23-generic x86_64)

root@biff-tutorial:~# bash
+ set -e
+ BIFF_ENV=prod

While runs, you'll be asked a few questions. For the first few questions, you can go with the defaults (i.e. just press Enter). When you get to part where tries to provision an SSL certificate for your domain (via the certbot command), you'll need to enter your domain and answer the other questions appropriately:

+ certbot --nginx
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Enter email address (used for urgent renewal and security notices)
 (Enter 'c' to cancel):

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at You must
agree in order to register with the ACME server. Do you agree?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: yes

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing, once your first certificate is successfully issued, to
share your email address with the Electronic Frontier Foundation, a founding
partner of the Let's Encrypt project and the non-profit organization that
develops Certbot? We'd like to send you email about our work encrypting the web,
EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: no
Account registered.
Please enter the domain name(s) you would like on your certificate (comma and/or
space separated) (Enter 'c' to cancel):
Requesting a certificate for

Successfully received certificate.

The entire process should take under 5 minutes. When it's done, run reboot.

7․ On your local machine, run git remote add prod ssh://

This is the last step, so after we add the remote, we can go ahead and deploy our app:

$ git remote add prod ssh://

$ git add .

$ git commit -m "Add the landing page"

$ bb deploy

After the command finishes, run bb logs and look for the System started message. This part may take a minute or so. Once you see it, you can load the website in your web browser!

Sending email

At this stage, when you sign in to eelchat, it will still print the sign-in link to the console instead of emailing it to you. You can get the sign-in link for your production app by running bb logs.

If you'd like to actually send the link via email (which you'll need to do at some point if you plan on having users), create a Postmark account. Once you have a Postmark API key and sending identity, add them to your config.edn and secrets.env files.

;; config.edn
{:prod {...
        :postmark/from ""
# secrets.env

Then register your site with reCAPTCHA. Select v2 Invisible for the reCAPTCHA type. Add your domain and localhost to the allowed domains. Then add your credentials to config.edn and secrets.env:

;; config.edn
{:prod {...
        :recaptcha/site-key "..."
# secrets.env

Then run bb deploy; bb restart to make the config change take effect. (Normally bb deploy would trigger a restart automatically; however, if you haven't made any new commits since the last deploy, bb deploy will upload your config files and then exit.)

Have a question? Join the #biff channel on Clojurians Slack.

Sign up for Biff: The Newsletter
Announcements, blog posts, et cetera et cetera.
This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.