# LFE's Dockerfile is one FROM line, its README banner says v2.2.1, and it ships three build systems

> A Lisp syntax front-end to the Erlang compiler, with an evaluator, a shell and twenty three example programs. The interesting files are the ones where the documentation, the release banner and the container build do not agree with each other.

**lfe/lfe** — Lisp Flavoured Erlang (LFE)

- Repository: https://github.com/lfe/lfe
- Stars: 2,458 · Forks: 146
- Language: Erlang
- License: Apache-2.0
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/lfe-lfe

## The repository Dockerfile compiles nothing at all

The whole file is a comment block and one instruction:

```
FROM lfex/lfe:latest
```

The comments explain why. LFE Docker images are based entirely upon the official Erlang Docker images, which include both Debian and Alpine variants, and the actual Dockerfiles live in a separate repository at lfex/dockerfiles. The last link in that comment block is pointed at for usage examples. The note also says the latest tag is an image containing the most recently released LFE and Erlang versions.

So the instruction pair `cd lfe` followed by `docker build .` produces an image that is a copy of the published one under a local tag, with nothing from your checkout inside it. A `.dockerignore` sits in the root, which suggests a build context that expects files to matter. The pull path is the one the documentation leads with: `docker pull lfex/lfe`, then `docker run -it lfex/lfe` for the REPL.

## The REPL banner in the docs still reports v2.2.1

Starting the shell from a clone is `./bin/lfe`, and the banner it prints is part of the documentation. It shows the VM it is running on, `Erlang/OTP 26 [erts-14.0.2] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] [dtrace]`, then a drawing around the words A Lisp-2+ on the Erlang VM, a docs pointer, a source pointer, and the version line `LFE v2.2.1 (abort with ^G)`.

Two observations. The newest release is v2.2.2, dated 2026-08-05, so the banner in the file is one release behind. And v2.2.2 and v2.2.1 were both published on 2026-08-05, ten minutes apart, which is the signature of a patch cut and re-cut rather than two separate pieces of work. The self-description as a Lisp-2+ is also worth noting, since Erlang keeps functions and variables in separate namespaces, and the claim is a compatibility statement as much as a slogan.

The same section documents how code is run rather than compiled. From a checkout it is `$ ./bin/lfe script-name script-arg-1 ...`, and from an installed copy it is `$ lfe script-name script-arg-1 ...`, which is how a script file takes arguments the way a shell script does.

## Three build systems, and the version lives in one file

The root carries a Makefile, an Emakefile, `rebar.config`, `rebar.config.script` and `get_comp_opts.escript`. Any of them can drive a build, and the release process needs all of them: `make tags` creates the release tags, the GitHub release is created by hand in the web interface, and `make publish` requires rebar3 installed plus an entry for the hex plugin in the system rebar.config.

The version itself has one home. The Makefile shells out to read it:

```
GET_VERSION = '{ok,[App]}=file:consult("src/$(LIB).app.src"), \
	V=proplists:get_value(vsn,element(3,App)), \
	io:format("~p~n",[V])' \
	$(FINISH)
```

That is why the documented release checklist starts with updating the version in `src/lfe.app.src`. One file to edit, and every tool reads it, which is the right shape for a project that has outlived several build systems.

## Install writes four programs and a man page tree

`make install` is the system-wide path. By default it creates `lfe`, `lfec`, `lfedoc` and `lfescript` in `/usr/local/bin`, and it also installs the man pages into the matching `$(PREFIX)/share/man/man*` directories. Both locations are variables: PREFIX sets the parent directory and MANINSTDIR sets the top man directory, so `make install PREFIX=/Users/rv/ MANINSTDIR=/Users/rv/man` puts the four programs in `/Users/rv/bin` and the pages under `/Users/rv/man`.

The defaults in the Makefile are `DESTDIR ?= /` and `PREFIX ?= /usr/local`, which means packaging tools can stage into a DESTDIR without editing the file. One prerequisite is not negotiable, and it is stated plainly: Erlang must be installed on the system and the `erl` binary must be in `$PATH`. Compiling is `make compile`, and the test suite is `make tests`.

The Makefile works out the operating system once, `OS_NAME := $(shell uname -s | tr '[:upper:]' '[:lower:]')`, and uses that single value only to decide whether to add the linker flag. Everything else about the install path is plain variables, which is what lets one target serve both a system install and a staged package.

## Ten topic manuals, including one for the Clojure reader

The documentation is plain text files in `doc/`, one per subsystem: `lfe.txt`, `lfescript.txt`, `lfe_bits.txt`, `lfe_clj.txt`, `lfe_comp.txt`, `lfe_docs.txt`, `lfe_gen.txt`, `lfe_io.txt`, `lfe_lib.txt` and `lfe_macro.txt`. Alongside them sit a user guide at `doc/user_guide.txt`, a version history at `doc/src/version_history.md`, and instructions for regenerating the docs after an edit at `doc/src/updating_docs.md`.

The existence of `lfe_clj.txt` is the interesting one, because the usage example in the README already uses Clojure style sugar:

```cl
lfe> (* 2 (lists:foldl #'+/2 0 (lists:seq 1 6)))
42
```

The `#'+/2` reader form is how a function is passed without naming it, and it is the same reader macro that dialect uses. LFE also ships an `emacs/` directory, so the editing side of a Lisp dialect is part of the repository rather than somebody's dotfiles.

There are three places to look for the same explanations: the text files in `doc/`, a gitbooks quick start, and the docs site at lfe.github.io that the usage section links to.

## The examples come in pairs that argue about style

There are twenty three files under `examples/`, and several of them are deliberately paired. `object-via-process.lfe` and `object-via-closure.lfe` implement the same idea twice, once with an Erlang process and once with a closure. `messenger.lfe` and `messenger-back.lfe` are the two halves of a two node conversation. `http-async.lfe` and `http-sync.lfe` show the same request both ways. `guessing-game.lfe` and `guessing-game2.lfe` are two attempts at one exercise.

The rest cover ground rather than argue: `church.lfe` for higher order functions, `core-macros.lfe`, `ets-demo.lfe`, `mnesia-demo.lfe`, `ping-pong.lfe`, `ring.lfe`, `fizzbuzz.lfe`, `gps1.lfe`, `lfe-eval.lfe`, `internal-state.lfe`, `joes-fav.lfe`, `simple-erl-exercises.lfe`, plus `sample-lfe-shellscript` and `sample-lfescript` for the two script styles. That pairing is the clearest statement of what the front-end is for: the same program, written in the idioms Erlang code can take.

## The compile flags bake in a relative path

Two flag variables carry the compiler settings: `ERLCFLAGS = -W1 +debug_info` for the Erlang sources, and `LFECFLAGS = -pa ../lfe +debug_info` for the LFE compiler itself, which is built from `$(BINDIR)/lfescript $(BINDIR)/lfec`. The second one hardcodes a relative path, `../lfe`, so the build expects a specific directory arrangement rather than resolving the code path from where it is invoked.

The C side is present too, with a `c_src/` directory alongside `include/`, `HOSTCC ?= $(CC)` and `CFLAGS ?= -Wall -Wextra`, plus `LDFLAGS ?= -Wl,--as-needed` applied only when the detected operating system is linux. Documentation handling checks for `mandb` with `MANDB = $(shell command -v mandb)`, so man page database updates follow whatever the host happens to have. The Makefile header carries a 2016 to 2025 copyright for Robert Virding.

One last detail explains why reading the version can be slow. `GET_VERSION` is not a text substitution: it starts a real Erlang node, consults the app.src file, prints the value and shuts down, with `FINISH=-run init stop -noshell` doing the stopping. The Makefile is written in the language it builds.

## Conclusion

LFE makes sense for a team that already runs Erlang and wants a Lisp surface over the same VM and the same modules, not for a team choosing a language. Read it with three checks in mind. The version banner in the documentation lags the newest release, so take the number from the tag rather than the screenshot. The container build in this repository does not compile anything, so a reproducible image has to come from the published lfex image or from the separate Dockerfiles repository. And the release path splits across three build systems, with rebar3 required for the hex.pm step, which matters if you script your own releases.

## FAQ

### What is LFE?

LFE, Lisp Flavoured Erlang, is a lisp syntax front-end to the Erlang compiler. Code produced with it is compatible with normal Erlang code, and an LFE evaluator and shell are included.

### How do I build LFE from a clone?

Clone the repository, change into the lfe directory and run make compile. Erlang must be installed and the erl binary must be in $PATH.

### Which programs does make install create for LFE?

Four: lfe, lfec, lfedoc and lfescript, in /usr/local/bin by default, with the man pages under share/man/man*.

### Does the Dockerfile in the LFE repository build LFE?

No. Its only instruction is FROM lfex/lfe:latest. The comments say the images are based on the official Erlang images and that the Dockerfiles themselves live in the lfex/dockerfiles repository.

### How do I run the LFE test suite?

Run make tests from the repository root.

### How is an LFE release published?

Update the version in src/lfe.app.src, run make tags to create both the number-only and the v-prefixed tag, create the GitHub release by hand, then run make publish, which needs rebar3 and a hex plugin entry in rebar.config.

## Sources

- [Issues](https://github.com/lfe/lfe/issues)
- [lfe/lfe on GitHub](https://github.com/lfe/lfe)
- [License: Apache-2.0](https://github.com/lfe/lfe/blob/develop/LICENSE)
- [README](https://github.com/lfe/lfe/blob/develop/README.md)
- [Releases](https://github.com/lfe/lfe/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/lfe-lfe
