Managing Different Environment Settings In Clojure With Leiningen
When developing applications in Clojure, one thing you might need is to be able to somehow keep different settings for each environment. For instance, you might want to be able to have different database settings for each environment.
This can be achieved with leiningen profiles:
:profiles {:dev-overrides {}
:dev
[:dev-overrides
{:dependencies [[com.cemerick/austin "0.1.5"]
[com.cemerick/piggieback "0.1.4-SNAPSHOT"]
[ring-mock "0.1.5"]]
:plugins [[com.keminglabs/cljx "0.5.0"]
[com.cemerick/austin "0.1.5"]
[lein-cljsbuild "1.0.3"]]}]
:test-overrides {}
:test
[:dev
:test-overrides]}
Assuming you have that :profiles sub-map somewhere in your project.clj map, it will override what's in the root of the project map.
For instance:
(defproject ring_to_curl "0.1.0-SNAPSHOT"
:description "FIXME: write description"
:url "http://example.com/FIXME"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.6.0"]
[cheshire "5.4.0"]]
:profiles {:dev
{:dependencies [[com.cemerick/austin "0.1.5"]
[com.cemerick/piggieback "0.1.4-SNAPSHOT"]
[ring-mock "0.1.5"]]
:plugins [[com.keminglabs/cljx "0.5.0"]
[com.cemerick/austin "0.1.5"]
[lein-cljsbuild "1.0.3"]]
:test {}}})
Notice how there's a :dependencies key in the root of the project map and there's one in the :dev profile.
What this means is whatever is in your currently active profile in your [:profiles :
Therefore, you can merge in dependencies like for say, browser REPLs in your dev profile. Merge in testing related dependencies in your test profile and so forth.
That's the simple case of leiningen profiles. There's a lot more you can do in the project.clj file.
Say I was using some library for databases and it was reading the database information from the project.clj map. I could then specify different databases for different environments.
I could have one for development, one for testing that gets rebuilt every time I run tests and so forth.
And this is fine for a single developer team.
What about for teams with multiple people?
This scenario comes up a lot more often then you would think. You could have a coworker that has a mysql database installed locally and another one that has their database installed in a docker container. They would have different connection information, usernames, and passwords.
In this scenario, one solution is to use what I call leiningen overrides.
There exists a file called profiles.clj in your ~/.lein/ directory. You might've played with it before if you're an emacs user to add the cider nrepl middleware to it.
This profile gets loaded for every single leiningen project you work with.
You can override what's in this folder by going to your project folder, the one that includes project.clj, and create another file named profiles.clj.
That profiles.clj in your project will take precedence over the one in your ~/.lein/ directory.
Also, notice that the map specified in the ~/.lein/profiles.clj file has a :user key (usually). That corresponds to the profile leiningen is using, which defaults to :user.
My ~/.lein/profiles.clj looks like this:
{:user {:plugins [[lein-ancient "0.6.2"]
[cider/cider-nrepl "0.9.0-SNAPSHOT"]
[lein-create-template "0.1.1"]]}}
Again, just like before, this map gets merged into the project.clj one. The :plugins key isn't some random key that is specific to the ~/.lein/profiles.clj file. It appears in your project.clj map.
You can add more profiles for each of your environments in your project.clj file or your project's profiles.clj file.
I make sure every environment my application will run in is defined in the project.clj file and then add overrides in the project's profiles.clj file.
This is how it's done:
project.clj
:profiles {:dev-overrides {}
:dev
[:dev-overrides
{:dependencies [[com.cemerick/austin "0.1.5"]
[com.cemerick/piggieback "0.1.4-SNAPSHOT"]
[ring-mock "0.1.5"]]
:plugins [[com.keminglabs/cljx "0.5.0"]
[com.cemerick/austin "0.1.5"]
[lein-cljsbuild "1.0.3"]]}]
:test-overrides {}
:test
[:dev
:test-overrides]}
There's a few concepts that need to be explained in this map.
Firstly, by specifying the submap :dev-overrides, which isn't a special name at all but one I defined myself, I can merge it into other maps in the profiles map.
If you look at the :dev, it's a vector. The vector has a list of maps that will be merged together. So, by first listing the keyword :dev-overrides, I'm saying, get the value of the keyword :dev-overrides, which in this case is an empty map, and put it here. The next element in the vector is a map, so it merges that with the empty map I had in there originally from :dev-overrides.
With the knowledge that you can merge in maps in the project map, and the knowledge of profiles.clj, you might start to see where I'm going.
Say your project.clj file contains that :profiles map I specified above with the :dev-overrides key. You can now create a profiles.clj file in your project folder like this:
profiles.clj
{:dev-overrides
{:dependencies [[reagent "0.5.0"]]}}
This is a simple, arbitrary case I came up with where I wanted the dependency reagent to be merged in to my :dev profile locally. Normally you might see things like AWS credentials and so forth, if for instance you're using weavejester's env library.
If you're working with other people, what I would do is make a file called profiles.clj.sample or something and commit that to git. Then when a new developer comes to your project, they can just check it out, rename it to profiles.clj and stick in their own settings to be merged in.
Master GitHub Actions with a Senior Infrastructure Engineer
As a senior staff infrastructure engineer, I share exclusive, behind-the-scenes insights that you won't find anywhere else. Get the strategies and techniques I've used to save companies $500k in CI costs and transform teams with GitOps best practices—delivered straight to your inbox.
Not sure yet? Check out the archive.
Unsubscribe at any time.