Docker image: hacklaen/opus-mt
Self-hosted OPUS-MT (Helsinki-NLP) translation server with a LibreTranslate-compatible HTTP API, packaged as a small, CPU-only Docker image.
- Drop-in backend for any client that speaks LibreTranslate's
/translateand/languagesendpoints. - Models are downloaded on demand from Hugging Face
(
Helsinki-NLP/opus-mt-{src}-{tgt}), converted to CTranslate2 INT8 on first use, and cached on a persistent volume. - Runs as a non-root user (UID 1100), exposes a single port (
8000), and ships with a built-in HTTP healthcheck. - No GPU, optional API-key protection — designed to live on an internal Docker network behind your own application.
Standalone OPUS-MT service for private, self-hosted translation workloads.
- You want a small CPU-only translation service with no external SaaS dependency.
- You already have a client that can talk to LibreTranslate-style endpoints.
- You prefer on-demand model downloads over baking many language pairs into a larger image.
| Tag | Description |
|---|---|
latest |
Latest stable release (currently identical to 1.1.0). |
1.1.0 |
Adds optional API-key protection via OPUS_MT_API_KEYS. |
1.0.0 |
First public release. Pin this if you need reproducibility. |
Images are published as a multi-arch manifest for linux/amd64
and linux/arm64.
docker run -d \
--name opus-mt \
-p 127.0.0.1:8000:8000 \
-v opus_mt_models:/models \
-e DEFAULT_PAIRS=de-en,en-de \
hacklaen/opus-mt:latestTranslate something:
curl -s http://127.0.0.1:8000/translate \
-H 'Content-Type: application/json' \
-d '{"q":["Guten Morgen, Welt!"],"source":"de","target":"en","format":"text"}'
# {"translatedText":["Good morning, world!"]}The first request for a new language pair triggers an automatic
conversion (Helsinki-NLP/opus-mt-de-en → CTranslate2 INT8) and
takes a few seconds. Subsequent requests are served from a hot
in-memory cache.
services:
opus-mt:
image: hacklaen/opus-mt:latest
restart: unless-stopped
environment:
DEFAULT_PAIRS: "de-en,en-de"
MAX_LOADED_PAIRS: "6"
volumes:
- opus_mt_models:/models
healthcheck:
test: ["CMD", "curl", "-fsS", "http://127.0.0.1:8000/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 20s
volumes:
opus_mt_models:In production, keep the service on an internal Docker network and reach
it from sibling containers via the service DNS name
(http://opus-mt:8000). Do not publish port 8000 to the public
Internet — see Security below.
The image speaks a subset of the LibreTranslate API. All endpoints return JSON.
Translate one or more strings.
Request
{
"q": ["Guten Morgen", "Wie geht es dir?"],
"source": "de",
"target": "en",
"format": "text"
}| Field | Type | Notes |
|---|---|---|
q |
string or [string] |
Input(s). Strings >256 tokens truncate. |
source |
string (ISO 639-1) |
Source language code. |
target |
string (ISO 639-1) |
Target language code. |
format |
"text" |
Only plain text is supported. |
Response
{ "translatedText": ["Good morning", "How are you?"] }Lists all language codes the running container can serve. Pairs
defined by Helsinki-NLP that don't exist will return HTTP 400
on /translate.
Returns the currently loaded (hot) pairs and the cache budget.
Cheap liveness probe (HTTP 200 + {"status":"ok"}) used by the
built-in Docker healthcheck.
| Variable | Default | Purpose |
|---|---|---|
DEFAULT_PAIRS |
(unset) | Comma-separated src-tgt pairs to eager-load on startup. |
OPUS_MT_API_KEYS |
false |
Set to true to require a valid API key on /translate, /languages and /models. |
OPUS_MT_API_KEYS_LIST |
(unset) | Comma-separated list of accepted API keys when OPUS_MT_API_KEYS=true. |
MAX_LOADED_PAIRS |
6 |
LRU cache size — how many converted models stay hot in RAM. |
HF_HOME |
/models/.hf-cache |
Hugging Face cache root (Helsinki-NLP downloads land here). |
TRANSFORMERS_CACHE |
/models/.hf-cache |
Mirror of HF_HOME for older transformers releases. |
Any pair you don't preload still works — it just pays a one-time download+conversion cost on first request.
To enable API-key protection, for example:
docker run -d \
--name opus-mt \
-p 127.0.0.1:8000:8000 \
-v opus_mt_models:/models \
-e OPUS_MT_API_KEYS=true \
-e OPUS_MT_API_KEYS_LIST=my-secret-key \
hacklaen/opus-mt:latestClients can then pass the key as api_key in the JSON body for
POST /translate, as a query parameter on GET /languages and
GET /models, or via the X-API-Key header.
The container expects /models to be writable by UID 1100
(the non-root opusmt user). With a named volume this is automatic.
With a bind-mount, prepare the host directory once:
sudo mkdir -p /srv/opus-mt-models
sudo chown -R 1100:1100 /srv/opus-mt-models
docker run ... -v /srv/opus-mt-models:/models hacklaen/opus-mt:latestA fully populated volume with the 13 default European languages (see below) ends up around 3–5 GB.
The container is bidirectionally configured for the following 13 ISO 639-1 codes:
de · en · fr · es · it · nl · pt · ru · et · pl · sv · da · fi
Any combination {src}-{tgt} for which Helsinki-NLP publishes a
model on Hugging Face works on-demand — so e.g. de-fr, en-ru,
pl-de etc. are all available without any image rebuild.
For the full catalogue of OPUS-MT pairs, see https://huggingface.co/Helsinki-NLP?search_models=opus-mt.
| Item | Typical value |
|---|---|
| Compressed image size | ~600 MB |
| On-disk image size | ~1.2 GB |
| RAM per loaded pair (INT8) | ~150–300 MB |
| Cold start (first pair) | 5–15 s (download) |
| Warm translation latency | 50–200 ms / sentence |
CPU is the only requirement. On 2 modern cores the server comfortably serves dozens of concurrent translation requests.
By default the image does not authenticate requests. There is no built-in rate limit and no TLS termination. By design it is meant to run as an internal service:
- Bind it to a private Docker network (no
ports:mapping). - Or, if you must publish it, bind to
127.0.0.1only and put it behind a reverse proxy (nginx, Caddy, Traefik) that adds auth and TLS.
If you need a lightweight in-container guard without a reverse proxy,
set OPUS_MT_API_KEYS=true and configure one or more keys via
OPUS_MT_API_KEYS_LIST.
If you need a publicly reachable instance, deploy LibreTranslate itself instead — it ships proper API-key handling.
- Private application backends on a Docker network
- Internal tools that need machine translation without a cloud API
- Reverse-proxied localhost services for desktop or homelab setups
- Batch jobs that translate plain text through a simple HTTP endpoint
This image is distributed under the GNU GPL v2 or later. See the
bundled LICENSE file for the full text and a summary of
the third-party components included in the runtime layer.
The model weights downloaded at runtime are published by the Helsinki-NLP OPUS-MT project under CC-BY 4.0. When you build a user-facing product on top of them, please carry the attribution forward:
Translations powered by Helsinki-NLP OPUS-MT models (CC-BY 4.0).
Dockerfile,requirements.txtandapp/are the full runtime source bundle for published releases.- The HTTP API intentionally stays close to LibreTranslate so existing clients can switch with minimal transport changes.
- The image is intended for private networks or reverse-proxied localhost bindings, not direct public exposure.