Self-host LanguageTool in Docker Compose

- Add erikvl87/languagetool service on the internal network
  (port 8010, English only by default — change langsToLoad to add more)
- 256 MB min / 512 MB max heap; healthcheck waits up to ~3 min for
  first startup while Java initialises
- Server LANGUAGETOOL_URL hardwired to http://languagetool:8010/v2 —
  no character limits, no rate limits, fully private
- Remove 40 000-char cap from lint route (not needed self-hosted)
- Bump fetch timeout to 30 s for cold-start requests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
chris 2026-05-24 21:43:43 -04:00
parent 55375d2ff0
commit bdafd3f2c3
2 changed files with 19 additions and 2 deletions

View File

@ -11,6 +11,7 @@ services:
- OLLAMA_URL=${OLLAMA_URL:-https://ai.binarygnome.com}
- OLLAMA_MODEL=${OLLAMA_MODEL:-gemma4:latest}
- OLLAMA_API_KEY=${OLLAMA_API_KEY:-}
- LANGUAGETOOL_URL=http://languagetool:8010/v2
extra_hosts:
- "host-gateway:host-gateway"
networks:
@ -22,6 +23,23 @@ services:
retries: 10
start_period: 15s
languagetool:
image: erikvl87/languagetool
restart: unless-stopped
environment:
- Java_Xms=256m
- Java_Xmx=512m
# Add more languages (comma-separated) or remove this line to load all
- langsToLoad=en
networks:
- internal
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost:8010/v2/languages > /dev/null"]
interval: 10s
timeout: 5s
retries: 18 # LT takes ~3060 s to start on first run
start_period: 90s
app:
build: ./frontend
restart: unless-stopped

View File

@ -11,7 +11,6 @@ router.use(auth)
router.post('/check', async (req, res) => {
const { text, language = 'en-US' } = req.body || {}
if (!text || typeof text !== 'string') return res.status(400).json({ error: 'text required' })
if (text.length > 40000) return res.status(400).json({ error: 'text too long (max 40 000 chars)' })
try {
const body = new URLSearchParams({
@ -25,7 +24,7 @@ router.post('/check', async (req, res) => {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
body: body.toString(),
signal: AbortSignal.timeout(20000),
signal: AbortSignal.timeout(30000),
})
if (!r.ok) {