Self-hosted service
Erikvl87/docker-languagetool avatar
Erikvl87/docker-languagetool

docker-languagetool and running LanguageTool as a container

Dockerfile for LanguageTool server

728 stars74 forksDockerfileLGPL-2.1

At a glance

What is it?
docker-languagetool is a Dockerfile that builds LanguageTool's server into an image published on Docker Hub as erikvl87/languagetool, with heap sizing, server options and n-gram data all driven by environment variables. It is a packaging project rather than a product, and its one real trap is that setting the FastText model variables makes the container exit immediately.
Who is it for?
docker-languagetool is a sensible choice for a team that wants proofreading as a service and would rather pull a published image than assemble a Java runtime and a LanguageTool distribution themselves, since the tag naming, the langtool_ prefixed options and the n-gram mounting are all documented.
Can I use it commercially?
Yes, with conditions. LGPL-2.1 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
Is it still maintained?
Yes. The repository last received commits 128 days ago.
What is it written in?
Mainly Dockerfile, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A Dockerfile for the LanguageTool server, published to Docker Hub

This repository is small on purpose. It contains a Dockerfile that creates a Docker image for LanguageTool, the open source proofreading software that covers English, French, German, Polish, Russian and more than twenty other languages. LanguageTool finds many errors that a simple spell checker cannot detect, which is the reason to run the server at all rather than shipping a dictionary.

The image is published on Docker Hub as erikvl87/languagetool, and the README points at that registry as the first setup option. The second option is to clone the repository and build the image yourself, which is the path to take if you intend to change the Dockerfile.

Two directories in the tree hint at the problems this project has solved. arm64-workaround suggests the ARM64 build needed special handling, and cors-workaround suggests the same for cross-origin requests. Both are the kind of detail that only shows up after someone has hit it in production.

The project is licensed LGPL-2.1 and describes itself as a Dockerfile rather than a framework. It publishes no GitHub releases, so the versioning story lives in Docker Hub tags instead.

Pulling the image and the port it actually listens on

The quickest start is two commands. Pull the image and run it with port 8010 mapped to 8010:

sh
docker pull erikvl87/languagetool 
docker run --rm -p 8010:8010 erikvl87/languagetool 

One detail catches people out later: the image is configured to listen on port 8010, which deviates from the default port of LanguageTool itself, 8081. Anything that talks to the server, including a reverse proxy you put in front of it, has to use 8010.

The check that the server is up is a plain HTTP request to the check endpoint:

sh
curl --data "language=en-US&text=a simple test" http://localhost:8010/v2/check 

A response listing matches and rule suggestions means the container started, the language model loaded and the HTTP server is answering.

If you would rather build from source, the sequence is a clone, a build and a run, and the clone includes a git configuration switch for line ending handling:

sh
git clone https://github.com/Erikvl87/docker-languagetool.git --config core.autocrlf=input 
docker build -t languagetool . 
docker run --rm -it -p 8010:8010 languagetool 

Tag naming: upstream releases plus a docker update suffix

The versioning scheme is worth understanding before you deploy, because it tells you what a rebuild of the same version means.

The image tags are derived from the official LanguageTool releases, so a tag maps to a known LanguageTool version. On top of that, changes to the Dockerfile for an already published version are released with a -dockerupdate-{X} postfix, where {X} is an incremental number.

The practical consequence is that two tags can contain the same LanguageTool but a different container setup. If you pin a plain upstream tag you get the original build of that version. If you pin the dockerupdate variant you get a rebuilt image for the same LanguageTool release, usually with a packaging fix rather than an upstream change. Both are legitimate, but they are not the same artefact, and a rollout that silently moves between them changes more than the tag name suggests.

The README also mentions that the pull command uses the latest tag by default and that you can specify a tag to pin onto a fixed version. For anything beyond a trial, pin it. A container whose image floats with latest will restart onto a different LanguageTool build whenever the host is recycled.

Heap sizing, the langtool_ prefix and the FastText trap

Configuration happens entirely through environment variables, in two families.

The first is the Java heap. LanguageTool is started with a minimal heap size of 256m and a maximum of 512m, and both defaults are overwritten through the Java_Xms and Java_Xmx environment variables:

sh
docker run --rm -it -p 8010:8010 -e Java_Xms=512m -e Java_Xmx=2g erikvl87/languagetool 

The second family covers LanguageTool's own HTTPServerConfig options. Any field of that configuration can be set by prefixing the field name with langtool_ and passing it as an environment variable, and the example combines one server option with two heap settings:

sh
docker run --rm -it -p 8010:8010 -e langtool_pipelinePrewarming=true -e Java_Xms=1g -e Java_Xmx=2g erikvl87/languagetool 

This is a clean design because it means the whole server configuration surface is reachable without editing files inside the image, which would otherwise require rebuilding it. The naming convention is also the documentation, since the option names map one to one onto the upstream HTTPServerConfig fields.

One thing the environment variable approach cannot express is anything that is not a server option or a JVM setting. Logging is the exception, and it is handled separately.

FastText is preinstalled in the image and the lid.176.bin model is downloaded automatically. That is convenient, and it comes with a sharp edge.

Because the image wires those values internally, providing the langtool_fasttextModel or langtool_fasttextBinary environment variables causes the container to exit with an error. This is the kind of failure that looks like a broken image rather than a configuration mistake, because the container starts, then dies, and the logs do not necessarily point at the variable you set.

The lesson generalises. The langtool_ prefix makes every upstream server option look like a free parameter, but some of them are already wired inside the image and setting them is a conflict rather than an override. Before you add an option, check whether the image already fixes a value for it.

Language support itself is a different matter. FastText is a language identification library, and the image ships it so that detection works out of the box rather than being left to the operator.

Overriding the logging configuration with a mounted logback.xml

LanguageTool's default logging configuration lives in the upstream logback.xml inside the image. Replacing it is done by mounting your own file over that path, and the mechanism is the same -v flag you would use for any other bind mount.

The file you write is a normal logback configuration, and the example given is as small as it can be:

xml
<?xml version="1.0" encoding="UTF-8"?> 
<configuration> 
  <logger name="org.languagetool" level="ERROR"/> 
</configuration> 

Then mount it:

sh
docker run --rm -it -p 8010:8010 -v /home/john/logback.xml:/LanguageTool/logback.xml erikvl87/languagetool 

The mount point is the LanguageTool directory inside the container, and the target file name is logback.xml. Note that the example does not mark the mount read-only, while the compose example does, so if you are copying from one to the other, decide deliberately.

This is the right pattern for a container that ships a service you do not control the configuration of. It means you can turn the log volume down to ERROR level without waiting for an upstream release that does it for you.

Mounting n-gram data for confusable word detection

LanguageTool can use large n-gram data sets to detect errors with words that are often confused, such as their and there. Those datasets are large, so they are not baked into the image. You download them to your local machine, unzip them into a local ngrams directory, and mount that directory at /ngrams in the container.

The layout matters, because the directory structure encodes the n-gram order and the language:

code
home/ 
โ”œโ”€ john/ 
โ”‚  โ”œโ”€ ngrams/ 
โ”‚  โ”‚  โ”œโ”€ en/ 
โ”‚  โ”‚  โ”‚  โ”œโ”€ 1grams/ 
โ”‚  โ”‚  โ”‚  โ”œโ”€ 2grams/ 
โ”‚  โ”‚  โ”‚  โ”œโ”€ 3grams/ 
โ”‚  โ”‚  โ”œโ”€ nl/ 
โ”‚  โ”‚  โ”‚  โ”œโ”€ 1grams/ 
โ”‚  โ”‚  โ”‚  โ”œโ”€ 2grams/ 
โ”‚  โ”‚  โ”‚  โ”œโ”€ 3grams/ 

Mounting is then combined with the languageModel option pointing at the /ngrams folder:

sh
docker run --rm -it -p 8010:8010 -e langtool_languageModel=/ngrams -v /home/john/ngrams:/ngrams:ro erikvl87/languagetool 

The mount is read-only in the example, which is right for data the server only reads. The volume adds real size to the host, and the benefit is limited to the confusion classes that n-gram data covers; for a deployment where that does not matter, leave it out.

Extending the spell checker with spelling.txt and prohibited.txt

LanguageTool can be taught additional words without touching the dictionary, and there are three files with different semantics. spelling.txt holds words the spell checker ignores and uses to generate corrections when someone types something similar. ignore.txt holds words the spell checker ignores without using them for corrections. prohibited.txt holds words that should be treated as incorrect even though the spell checker would accept them.

The documented way to add a word is to extend the file with a custom Dockerfile layered on the published image:

dockerfile
FROM erikvl87/languagetool 
 
# Improving the spell checker 
# http://wiki.languagetool.org/hunspell-support 
USER root 
COPY en_spelling_additions.txt en_spelling_additions.txt 
RUN  (echo; cat en_spelling_additions.txt) >> org/languagetool/resource/en/hunspell/spelling.txt 
USER languagetool 

The switch to USER root and back to USER languagetool is what allows the file to be modified, and the base image deliberately runs as a non-root user.

Two details are easy to get wrong. The target path is language-specific, so adding words to another language means changing the en tag in the path. And for some languages the file is not in the hunspell folder: for Dutch the path is org/languagetool/resource/nl/spelling/spelling.txt. Getting that path wrong produces a build that appends to a file nothing reads, and the words silently do not take effect.

What the image is built from, and how the maintenance window works

The Dockerfile builds LanguageTool from source rather than downloading a distribution. The build stage starts from debian:bookworm, installs a toolchain including openjdk-17-jdk-headless, maven, git, unzip and xmlstarlet, then clones the LanguageTool repository at the version given by the LANGUAGETOOL_VERSION argument, which defaults to 6.8:

dockerfile
# syntax=docker/dockerfile:1
ARG LANGUAGETOOL_VERSION=6.8
ARG TARGETARCH
FROM debian:bookworm AS build
ENV DEBIAN_FRONTEND=noninteractive
RUN git clone https://github.com/languagetool-org/languagetool.git --depth 1 -b v${LANGUAGETOOL_VERSION}
WORKDIR /languagetool
RUN --mount=type=cache,target=/root/.m2 mvn --projects languagetool-standalone --also-make package -DskipTests --quiet

The ARM64 branch installs a separate set of packages, and the TARGETARCH argument is what selects it. The xmlstarlet dependency has a specific purpose: the build reads the Maven revision property out of pom.xml to work out which zip the standalone module produced, then unzips it into /dist.

The consequence for operators is that a new LanguageTool release means a new image, not a new tag on an existing one, and the repository has no GitHub releases to point at. The last push was on 2026-06-17, and the repository is not archived. With the LGPL-2.1 licence, distributing the image means you take on the obligations that licence carries, so read the terms if you plan to republish it internally with modifications.

Editorial conclusion

docker-languagetool is a sensible choice for a team that wants proofreading as a service and would rather pull a published image than assemble a Java runtime and a LanguageTool distribution themselves, since the tag naming, the langtool_ prefixed options and the n-gram mounting are all documented. It is the wrong choice if you need the server on a host without Docker, or if you need a LanguageTool version newer than the ones published here, because the image tracks upstream releases rather than your build. Before deploying, pin a specific tag instead of latest, check that Java_Xmx fits the memory your container actually has, and read the FastText note so you do not set langtool_fasttextModel and get a container that refuses to start.

Frequently asked questions

How do I run LanguageTool in Docker using the published image?

Pull erikvl87/languagetool from Docker Hub and run it with port 8010 published on the host, for example docker run --rm -p 8010:8010 erikvl87/languagetool. The image listens on 8010, not the default LanguageTool port of 8081.

How do I configure the heap size of the LanguageTool container?

Set the Java_Xms and Java_Xmx environment variables, which override the defaults of 256m and 512m. For example, docker run --rm -it -p 8010:8010 -e Java_Xms=512m -e Java_Xmx=2g erikvl87/languagetool.

Why does the container exit when I set the FastText environment variables?

FastText is preinstalled in the image and its lid.176.bin model is downloaded automatically, so the values are wired internally. Providing langtool_fasttextModel or langtool_fasttextBinary causes the container to exit with an error.

How do I add my own words to the LanguageTool spell checker?

Build a custom Dockerfile on top of erikvl87/languagetool that appends your word list to the spelling.txt file for your language, then build and run it. Note that for some languages such as nl the file sits in a spelling folder rather than a hunspell folder.

Official sources

  1. Erikvl87/docker-languagetool on GitHub
  2. Issues
  3. License: LGPL-2.1
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/erikvl87-docker-languagetool.svg)](https://hysenlabs.com/projects/erikvl87-docker-languagetool)

Was this useful?