Official Docker images and Docker Compose configurations for Fess, an open-source enterprise search server built on OpenSearch.
Fess is an enterprise search server with a web-based administration UI, indexing web sites, file systems, and databases. It searches through OpenSearch, which runs as its own container here -- the compose files start both, so you do not have to install or operate OpenSearch by hand. For full documentation, see the official Fess site.
- Full-text search with faceted navigation, filtering, and suggestions
- Built-in crawlers for web pages, file systems, and databases
- Indexing for MS Office, PDF, HTML, XML, CSV, and 40+ other file formats
- Authentication and access control via LDAP, Active Directory, and SSO
- Web-based administration for crawlers, schedules, and dictionaries
- Distributed architecture with horizontal scaling
- Search Engine: OpenSearch 3.x
- Application Server: Apache Tomcat (embedded)
- Runtime: Java 21 (Eclipse Temurin)
- Base Images: Alpine Linux (production), Amazon Linux 2023, Ubuntu Noble
- Containerization: Docker & Docker Compose
- Optional Services: OpenSearch Dashboards, MinIO object storage
System Requirements:
- Docker and Docker Compose installed
- At least 4GB RAM available
- For OpenSearch:
vm.max_map_count>= 262144
Set vm.max_map_count (Linux/WSL):
# Temporary
sudo sysctl -w vm.max_map_count=262144
# Permanent
echo 'vm.max_map_count=262144' | sudo tee -a /etc/sysctl.confThe Compose files live in the compose/ directory, so clone the repository and move there first. The commands that follow assume that working directory unless they change it themselves.
# Clone the repository
git clone https://github.com/codelibs/docker-fess.git
cd docker-fess/compose
# Start Fess with OpenSearch
docker compose -f compose.yaml -f compose-opensearch3.yaml up -dFess is then available at http://localhost:8080.
docker compose -f compose.yaml -f compose-opensearch3.yaml -f compose-dashboards3.yaml up -dFess is at http://localhost:8080 and OpenSearch Dashboards at http://localhost:5601.
docker compose -f compose.yaml -f compose-opensearch3.yaml -f compose-minio.yaml up -dFess is at http://localhost:8080 and the MinIO console at http://localhost:9001.
The snapshot stack runs the in-development Fess build. compose/snapshot/compose.yaml only defines the Fess service, so it has to be combined with a backend, exactly like compose.yaml:
cd docker-fess/compose/snapshot
# Single OpenSearch node
docker compose -f compose.yaml -f ../compose-opensearch3.yaml up -d
# Five-node OpenSearch cluster
docker compose -f compose.yaml -f compose-cluster.yaml up -d-
Access Fess Admin: Navigate to http://localhost:8080/admin
- Default credentials:
admin/admin
- Default credentials:
-
Create a Web Crawler:
- Go to Crawler > Web
- Add URL:
https://example.com/* - Start crawling from System > Scheduler
-
Configure Search:
- Set crawl schedules, filters, and permissions
- Monitor crawl status and logs
Configure Fess behavior through environment variables:
environment:
# Search backend configuration
- SEARCH_ENGINE_HTTP_URL=http://search01:9200
# Dictionary and data paths
- FESS_DICTIONARY_PATH=/usr/share/opensearch/config/dictionary/
# Heap size (see Memory Settings below)
- FESS_HEAP_SIZE=1g
# Extra JVM options. Do not put -Xms / -Xmx here.
- FESS_JAVA_OPTS=-Djavax.net.ssl.trustStore=/opt/fess/truststore.jks
# Plugin installation
- FESS_PLUGINS=fess-ds-wikipedia:15.8.0 fess-ds-git:15.8.0The full set the images understand:
| Variable | Default | Description |
|---|---|---|
SEARCH_ENGINE_HTTP_URL |
http://localhost:9200 |
Backend search engine URL |
SEARCH_ENGINE_TYPE |
(none) | Set to cloud for a plain OpenSearch without the Fess plugins. That mode loses the Japanese analyzers, minhash near-duplicate detection and the dictionary admin UI |
SEARCH_ENGINE_USERNAME |
(none) | Username, when the search engine requires authentication |
SEARCH_ENGINE_PASSWORD |
(none) | Password, when the search engine requires authentication |
FESS_DICTIONARY_PATH |
/var/lib/opensearch/config/ |
Dictionary directory shared with OpenSearch |
FESS_PORT |
8080 |
Port Fess listens on inside the container |
FESS_CONTEXT_PATH |
/ |
Context path Fess is served under, e.g. /fess |
FESS_HEAP_SIZE |
512m |
Heap size; see Memory Settings below |
FESS_MIN_MEM / FESS_MAX_MEM |
(none) | Asymmetric heap bounds; see Memory Settings below |
FESS_JAVA_OPTS |
(none) | Extra JVM options, mainly -Dfess.config.* and -Dfess.system.* |
FESS_PLUGINS |
(none) | Space-separated plugin-name:version list to install at startup |
FESS_CONF_PATH |
/etc/fess |
Configuration directory |
FESS_OVERRIDE_CONF_PATH |
/opt/fess |
Extra configuration directory placed ahead of FESS_CONF_PATH on the classpath, so a file mounted here replaces the packaged one |
PING_INTERVAL |
60 |
Seconds between the entrypoint's health probes |
PING_RETRIES |
5 |
Consecutive failed probes, once Fess has answered at least once, before the entrypoint gives up and the container exits |
PING_STARTUP_RETRIES |
10 |
Failed probes allowed before Fess has answered for the first time |
The health check builds its URL from FESS_PORT and FESS_CONTEXT_PATH, so moving Fess to another port or context path does not make the container report unhealthy.
Startup is counted separately from an outage. Fess has not answered yet while it is still starting, and on a loaded host that takes longer than a running instance ever goes unanswered; counting those probes against PING_RETRIES used to kill a container that was only slow to come up. With the defaults, Fess has ten minutes to answer for the first time, and five consecutive failed probes after that end the container.
FESS_JAVA_OPTS is split on whitespace, so an option whose value contains a space breaks the JVM command line: the container fails with Could not find or load main class and then exits. Options with such a value belong in /opt/fess/fess_config.properties (see FESS_OVERRIDE_CONF_PATH) instead.
The heap comes from FESS_HEAP_SIZE, or from the FESS_MIN_MEM / FESS_MAX_MEM pair when you want an asymmetric heap:
| Variables | Resulting JVM flags |
|---|---|
FESS_HEAP_SIZE=1g |
-Xms1g -Xmx1g |
FESS_MIN_MEM=512m and FESS_MAX_MEM=2g |
-Xms512m -Xmx2g |
| both of the above | -Xms1g -Xmx1g; FESS_HEAP_SIZE wins |
| none of them | -Xms512m -Xmx512m, the container default |
Set both halves of the pair. An unset half falls back to the Fess default (256m for the minimum, 2g for the maximum), not to the container default.
Do not pass -Xms or -Xmx through FESS_JAVA_OPTS. Fess appends the pair it derives from the variables above after everything in FESS_JAVA_OPTS, and the JVM keeps the last value it is given, so heap flags placed there are discarded without a warning.
Run multiple Fess instances sharing one OpenSearch cluster:
cd docker-fess/compose/multi-instance
# Start OpenSearch + 2 Fess instances
docker compose -f compose.yaml -f compose-fess01.yaml -f compose-fess02.yaml up -dInstance 1 is at http://localhost:8080 and instance 2 at http://localhost:8081. Each instance uses separate indices for data isolation.
| Service | URL | Purpose |
|---|---|---|
| Fess Web UI | http://localhost:8080 | Main search interface |
| Fess Admin | http://localhost:8080/admin | Administration panel |
| OpenSearch API | http://localhost:9200 | Direct search engine access |
| OpenSearch Dashboards | http://localhost:5601 | Data visualization |
| MinIO Console | http://localhost:9001 | Object storage management |
Fess Application:
# Build specific version
docker build --rm -t ghcr.io/codelibs/fess:15.8.0 ./fess/15.8/
# Build with custom args
docker build --build-arg FESS_VERSION=15.8.0 -t my-fess ./fess/15.8/OpenSearch with Fess Plugins:
# Build OpenSearch image
docker build --rm -t ghcr.io/codelibs/fess-opensearch:3.8.0 ./opensearch/3.8/docker-fess/
├── fess/ # Fess Docker images
│ ├── 15.8/ # Latest stable version
│ ├── 15.7/ # Previous versions
│ └── snapshot/ # Development builds
├── opensearch/ # OpenSearch images with Fess plugins
│ ├── 3.8/ # Latest OpenSearch
│ └── 3.7/ # Previous versions
├── elasticsearch/ # Elasticsearch images (legacy)
└── compose/ # Docker Compose configurations
├── compose.yaml # Base Fess service
├── compose-opensearch3.yaml # OpenSearch 3.x
├── compose-dashboards3.yaml # OpenSearch Dashboards
├── compose-minio.yaml # MinIO object storage
├── multi-instance/ # Multi-instance setup
├── snapshot/ # Development snapshot builds
└── vanilla/ # Stock OpenSearch, without the Fess plugins
Key Configuration Locations:
/etc/fess/- Main configuration directory/opt/fess/- Custom configuration overrides/var/log/fess/- Application logs/var/lib/fess/- Variable data storage
Custom Index Configuration:
# Configure separate indices for multi-instance
FESS_JAVA_OPTS="-Dfess.config.index.document.search.index=myapp.search \
-Dfess.config.index.document.update.index=myapp.update \
-Dfess.config.index.config.index=myapp_config"| Fess Version | OpenSearch | Elasticsearch | Java | Base Image |
|---|---|---|---|---|
15.9.0-SNAPSHOT (snapshot tag) |
3.8.0 | - | 21 | Alpine/Ubuntu Noble/Amazon Linux 2023 |
| 15.8.0 | 3.8.0 | - | 21 | Alpine/Ubuntu Noble/Amazon Linux 2023 |
| 15.7.0 | 3.7.0 | - | 21 | Alpine/Ubuntu Noble/Amazon Linux 2023 |
| 15.6.0 | 3.6.0 | - | 21 | Alpine/Ubuntu Noble/Amazon Linux 2023 |
| 15.5.1 | 3.5.0 | - | 21 | Alpine/Ubuntu Noble/Amazon Linux 2023 |
| 15.4.0 | 3.4.0 | - | 21 | Alpine/Ubuntu Noble/Amazon Linux 2023 |
| 15.3.0 | 3.3.0 | - | 21 | Alpine/Ubuntu Noble/Amazon Linux 2023 |
| 15.2.0 | 3.2.0 | - | 21 | Alpine/Ubuntu Noble/Amazon Linux 2023 |
| 15.1.0 | 3.1.0 | - | 21 | Alpine/Ubuntu Noble/Amazon Linux 2023 |
| 15.0.0 | 2.15 | 8.10+ | 17 | Alpine |
| 14.x | 2.x | 7.17/8.x | 11 | Alpine |
Fess 15.9 removed the embedded search engine, so a Fess container always needs an OpenSearch to talk to. The compose files here already start one, so nothing changes for them, but three behaviours differ:
- A search engine older than OpenSearch 3 now stops startup instead of logging an error and
continuing. Earlier engines do not implement the
_shard_docsort that document export, purge, backup and suggest dictionary builds rely on, and over HTTP those requests hang rather than fail, so the mismatch is refused up front. SEARCH_ENGINE_HOMEno longer does anything. It pointed at the bundled engine's directory.- The legacy
elasticsearch.*configuration keys are gone. TheES_*environment variables these images accept are unaffected; the entrypoint translates them toSEARCH_ENGINE_*.
15.9 also moved several features out of the war and into plugins. The images install all seven
at build time with bin/fess-setup install plugin, so they behave as the 15.8 images did and
none of these belongs in FESS_PLUGINS:
| What stops working without it | Plugin |
|---|---|
script_type=groovy -- and any data store, job or crawler script that leaves script_type unset, which still resolves to groovy |
fess-script-groovy |
s3: crawling and the S3 storage backend |
fess-storage-s3 |
gcs: crawling and the GCS storage backend |
fess-storage-gcs |
sso.type=saml |
fess-sso-saml |
sso.type=spnego |
fess-sso-spnego |
sso.type=entraid, and the legacy aad |
fess-sso-entraid |
sso.type=oic -- the one name that differs from its plugin's |
fess-sso-oidc |
To drop one from an image, run bin/fess-setup remove plugin <name> in a derived image. Keep a
plugin on the same line as the Fess it runs in: a 15.9 SSO plugin in a 15.8 Fess registers a
second copy of an authenticator that war already declares, and /sso/ then fails.
Container fails to start:
# Check vm.max_map_count
cat /proc/sys/vm/max_map_count # Should be >= 262144
# Check container logs
docker compose logs fess01
docker compose logs search01Out of memory errors:
Raise the heap in the environment: block of the Fess service. Exporting the variable in your shell has no effect, because compose.yaml does not pass the host environment through:
services:
fess01:
environment:
- "FESS_HEAP_SIZE=2g"Recreate the container afterwards so the new value is applied.
Search not working:
# Verify OpenSearch connection
curl http://localhost:9200/_cluster/health
# Check Fess connectivity
docker compose exec fess01 curl http://search01:9200Data persistence:
# List volumes
docker volume ls | grep compose
# Remove volumes (WARNING: deletes data)
docker volume rm compose_search01_data compose_search01_dictionaryFor production deployments:
-
Memory allocation:
- Fess: 2-4GB heap (
FESS_HEAP_SIZE=2g) - OpenSearch: 50% of available RAM (
OPENSEARCH_JAVA_OPTS=-Xms2g -Xmx2g)
- Fess: 2-4GB heap (
-
Storage optimization:
- Use SSD storage for OpenSearch data volumes
- Separate OS disk from data volumes
-
Network configuration:
- Use dedicated network for container communication
- Configure proper DNS resolution
Install additional Fess plugins:
environment:
- FESS_PLUGINS=fess-ds-wikipedia:15.8.0 fess-ds-git:15.8.0Entries are plugin-name:version pairs separated by spaces, and the name has to start with one of fess-ds-, fess-ingest-, fess-llm-, fess-script-, fess-sso-, fess-storage-, fess-theme-, fess-thumbnail- or fess-webapp-. A name that is not recognized, or a version that cannot be downloaded, is skipped and does not stop the container from starting, so check the boot log after adding a plugin.
Semantic search no longer needs a plugin. It became part of Fess in 15.8, and fess-webapp-semantic-search is not published for 15.8 or later.
When TLS is terminated at a proxy in front of Fess, the container still speaks plain HTTP, and two settings have to be told about the public origin.
services:
fess01:
environment:
- "FESS_JAVA_OPTS=-Dfess.config.session.cookie.secure=true -Dfess.config.theme.api.csrf.server.origins=https://fess.example.com"session.cookie.secure ships blank, which leaves the Secure attribute to Tomcat, and Tomcat only adds it when the request it sees is HTTPS. Behind a TLS-terminating proxy that request is HTTP, so JSESSIONID goes out without Secure unless this is set to true. Keep it blank for plain-HTTP development, because the browser will not send the cookie back over HTTP once it is set.
theme.api.csrf.server.origins is the list of origins the v2 API treats as its own. It also ships blank, and the API then reconstructs the expected origin from the request, which does not match the proxy's public origin. POST /api/v2/login through a proxy is rejected with cross-site request blocked until the public origin is listed here.
Fess has no key store option of its own, so terminate TLS at the proxy rather than in the container.
# Backup OpenSearch data
docker run --rm -v compose_search01_data:/data -v $(pwd):/backup alpine tar czf /backup/opensearch-backup.tar.gz /data
# Restore OpenSearch data
docker run --rm -v compose_search01_data:/data -v $(pwd):/backup alpine tar xzf /backup/opensearch-backup.tar.gz -C /Copyright 2016-2026 CodeLibs Project and the Others.