abogoyavlensky / clojure-kamal-example

Full-stack Clojure/Script setup with deployment using Kamal
MIT License
9 stars 1 forks source link
automigrate ci clojure clojurescript database deploy full-stack kamal migrations postgresql

Full-stack Clojure/Script app with deployment using Kamal

This project demonstrates the setup of a Clojure/Script web application that uses PostgreSQL as a database. It also includes configuration for deploying the app with Kamal on a single server.

Key backend libs:

Key frontend libs:

Other tools:

The app

This setup provides a Clojure/Script web application with an example API route for demonstration purposes with fetching a list of movies and displaying them on the main page.

App main page

Deploy: summary

Pre-requisites

Install Kamal locally

Install mise-en-place (or asdf), and run:

brew install libyaml  # or on Ubuntu: `sudo apt-get install libyaml-dev` 
mise install ruby
gem install kamal -v 1.5.2
kamal version

First deployment

kamal envify --skip-push  # :warning: then fill all variables in the newly created `.env` file
kamal server bootstrap
ssh root@192.168.0.1 'docker network create traefik'
ssh root@192.168.0.1 'mkdir -p /root/letsencrypt && touch /root/letsencrypt/acme.json && chmod 600 /root/letsencrypt/acme.json'
kamal setup
kamal app exec 'java -jar standalone.jar migrations'

Regular deployment

kamal deploy

or push to the master branch.

Deploy: step-by-step

Assume that you have Kamal installed and other requirements from the "Pre-requisites" section above.


:information_source: Note: Alternatively you can use dockerized version of Kamal and use the ./kamal.sh predefined command instead of Ruby gem version:

./kamal.sh version

It mostly works for initial server setup, but some management commands don't work properly. For instance, ./kamal.sh app logs -f or ./kamal.sh build push.


Initial server setup

Setup environment variables

Run command envify to create a .env with all required empty variables:

kamal envify --skip-push

The --skip-push parameter prevents the .env file from being pushed to the server.

Now, you can fill all environment variables in the .env file with actual values for deployment on the server. Here’s an example:

# Generated by kamal envify
# DEPLOY
SERVER_IP=192.168.0.1
REGISTRY_USERNAME=your-username
REGISTRY_PASSWORD=secret-registry-password
TRAEFIK_ACME_EMAIL=your_email@example.com
APP_DOMAIN=app.domain.com

# App
DATABASE_URL="jdbc:postgresql://clojure-kamal-example-db:5432/demo?user=demoadmin&password=secret-db-password"

# DB accessory
POSTGRES_DB=demo
POSTGRES_USER=demoadmin
POSTGRES_PASSWORD=secret-db-password

Notes:

:warning: Do not include file .env to git repository!

Bootstrap server and deploy app

Install Docker on a server:

kamal server bootstrap

Create a Docker network for access to the database container from the app by container name and a directory for Let’s Encrypt certificates:

ssh root@192.168.0.1 'docker network create traefik'
ssh root@192.168.0.1 'mkdir -p /root/letsencrypt && touch /root/letsencrypt/acme.json && chmod 600 /root/letsencrypt/acme.json'

Set up Traefik, the database, environment variables and run app on a server:

kamal setup

The app is deployed on the server, but it is not fully functional yet. You need to run database migrations:

kamal app exec 'java -jar standalone.jar migrations'

Now, the application is fully deployed on the server.

Regular deploy

For subsequent deployments from the local machine, run:

kamal deploy

Or just push to the master branch, there is a GitHub Actions pipeline that does the deployment automatically .github/workflows/deploy.yaml.

Setup CI for deployment

For CI setup you need to add following environment variables as secrets for Actions. In GitHub UI of the repository navigate to Settings -> Secrets and variables -> Actions. Then add variables with the same values you added to local .env file:

APP_DOMAIN
DATABASE_URL
POSTGRES_DB
POSTGRES_PASSWORD
POSTGRES_USER
SERVER_IP
SSH_PRIVATE_KEY
TRAEFIK_ACME_EMAIL

Notes

To generate SSH keys, run:

ssh-keygen -t ed25519 -C "your_email@example.com"

Development

System deps

Install mise-en-place (or asdf), then to install system deps run:

mise install

Frontend

Run frontend in watch mode (js and css):

task ui

Backend

Create file .env.local with local database credentials, for example:

POSTGRES_DB=demo
POSTGRES_USER=demo
POSTGRES_PASSWORD=demo
DATABASE_URL=jdbc:postgresql://localhost:5432/demo?user=demo&password=demo

⚠️ Do not include file .env.local to git repository!

Run database for local development:

task up

Start REPL:

task repl

Run backend in the REPL:

(reset)

Run tests:

task test

Manage database migrations:

task migrations -- list
task migrations -- make
task migrations -- migrate
task migrations -- explain :number 1

Print all available commands:

task -l

task: Available tasks for this project:
* build:                Build uberjar
* check:                Run lint, fmt and tests. Intended to use locally
* css-prod:             Build css in prod mode
* deps:                 Install all dev deps
* fmt:                  Fix code formatting
* fmt-check:            Check code formatting
* lint:                 Linting project's code
* lint-init:            Linting project's classpath
* migrations:           Manage db migrations
* outdated:             Upgrade outdated Clojure deps versions
* outdated-check:       Check outdated deps versions
* repl:                 Run built-in Clojure repl
* test:                 Run tests
* ui:                   Build js and css in watch mode for local development
* up:                   Run docker services for local development

Resources

License

Copyright © 2024 Andrey Bogoyavlenskiy

Distributed under the MIT License.