Graylog on Docker
Graylog is an open-source centralized log management platform — ingest syslog, GELF, Beats, and raw TCP/UDP streams, then search, parse, alert, and dashboard across an entire fleet from one web interface. This guide deploys the official stack with Docker Compose: MongoDB for configuration and metadata, the Graylog Data Node (a managed OpenSearch that handles log storage and search), and the Graylog server itself, which now starts behind a preflight UI for first-time setup.
Stack: graylog · graylog-datanode · mongodb · Inputs: Syslog · GELF · Beats · RAW · OS: Linux
01 — Prerequisites
| Requirement | Notes |
| Docker + Compose | Docker Engine v20.10.10+ and the Compose v2 plugin |
| Linux server | At least 4 GB RAM; the Data Node (OpenSearch under the hood) is the memory-hungry component |
| vm.max_map_count | Kernel setting must be raised to 262144 or the Data Node will refuse to start — covered in step 02 |
| Open ports | 9000/TCP (web & API); plus whichever input ports you enable, e.g. 5140 (syslog), 12201 (GELF), 5044 (Beats) |
⚠ Do not point Graylog at OpenSearch 3.0+ or Elasticsearch — they are unsupported and will break the instance. The Data Node ships the correct, supported search backend for you, which is why the modern stack uses it instead of a hand-managed OpenSearch container.
02 — Prepare the Host
The Data Node embeds OpenSearch, which requires an elevated virtual-memory map count. Set it on the host before the first start, and make it persistent so it survives reboots.
Raise vm.max_map_count
echo "vm.max_map_count=262144" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
Verify
cat /proc/sys/vm/max_map_count
# should output: 262144
ℹ Skipping this is the single most common reason a fresh Graylog stack fails to come up — the Data Node logs a bootstrap check failure and exits. Set it first and the rest is straightforward.
03 — Project Files
You need just two files in a working directory: the Compose file from the official repository, and an .env file that supplies the two mandatory secrets. Grab the open-core Compose file from Graylog's repo as your starting point.
Directory layout
graylog/
├── docker-compose.yml ← from Graylog2/docker-compose (open-core)
└── .env ← the two required secrets
Fetch the example Compose file
mkdir graylog && cd graylog
wget -O docker-compose.yml \
https://raw.githubusercontent.com/Graylog2/docker-compose/main/open-core/docker-compose.yml
04 — Generate the Two Secrets
Graylog requires two values, both consumed from .env. The password secret is a long random string used to pepper stored passwords and encrypt sensitive values — it must be identical across all nodes and must never change after install. The root password hash is the SHA-256 of your chosen admin password.
Generate the password secret (64+ chars)
openssl rand -hex 32
Generate the SHA-256 of your admin password
echo -n 'your-admin-password' | sha256sum | awk '{print $1}'
.env
GRAYLOG_PASSWORD_SECRET=<paste output of openssl rand -hex 32>
GRAYLOG_ROOT_PASSWORD_SHA2=<paste output of the sha256sum command>
⚠ Changing GRAYLOG_PASSWORD_SECRET after installation invalidates every user session and every encrypted value already in the database (such as stored access tokens). Generate it once and treat it as permanent. The root password hash cannot be changed through the web UI or API — it is your break-glass login.
05 — Review the Compose File
The official open-core file defines three services. MongoDB stores configuration; the Data Node provides the search backend; the Graylog server runs the web interface and processing pipeline. Both Graylog and the Data Node read the same GRAYLOG_PASSWORD_SECRET. The structure below is the upstream example, lightly abbreviated.
docker-compose.yml (abbreviated)
services:
mongodb:
image: "mongo:7.0"
restart: "on-failure"
networks:
- graylog
volumes:
- "mongodb_data:/data/db"
- "mongodb_config:/data/configdb"
datanode:
image: "${DATANODE_IMAGE:-graylog/graylog-datanode:7.1}"
hostname: "datanode"
environment:
GRAYLOG_DATANODE_NODE_ID_FILE: "/var/lib/graylog-datanode/node-id"
GRAYLOG_DATANODE_PASSWORD_SECRET: "${GRAYLOG_PASSWORD_SECRET:?Please configure GRAYLOG_PASSWORD_SECRET in the .env file}"
GRAYLOG_DATANODE_MONGODB_URI: "mongodb://mongodb:27017/graylog"
ulimits:
memlock:
hard: -1
soft: -1
nofile:
soft: 65536
hard: 65536
ports:
- "127.0.0.1:8999:8999/tcp" # DataNode API
- "127.0.0.1:9200:9200/tcp"
- "127.0.0.1:9300:9300/tcp"
networks:
- graylog
volumes:
- "graylog-datanode:/var/lib/graylog-datanode"
restart: "on-failure"
graylog:
hostname: "server"
image: "${GRAYLOG_IMAGE:-graylog/graylog:7.1}"
depends_on:
mongodb:
condition: "service_started"
datanode:
condition: "service_started"
entrypoint: "/usr/bin/tini -- /docker-entrypoint.sh"
environment:
GRAYLOG_NODE_ID_FILE: "/usr/share/graylog/data/data/node-id"
GRAYLOG_PASSWORD_SECRET: "${GRAYLOG_PASSWORD_SECRET:?Please configure GRAYLOG_PASSWORD_SECRET in the .env file}"
GRAYLOG_ROOT_PASSWORD_SHA2: "${GRAYLOG_ROOT_PASSWORD_SHA2:?Please configure GRAYLOG_ROOT_PASSWORD_SHA2 in the .env file}"
GRAYLOG_HTTP_BIND_ADDRESS: "0.0.0.0:9000"
GRAYLOG_HTTP_EXTERNAL_URI: "http://localhost:9000/"
GRAYLOG_MONGODB_URI: "mongodb://mongodb:27017/graylog"
ports:
- "127.0.0.1:5044:5044/tcp" # Beats
- "127.0.0.1:5140:5140/udp" # Syslog
- "127.0.0.1:5140:5140/tcp" # Syslog
- "127.0.0.1:5555:5555/tcp" # RAW TCP
- "127.0.0.1:9000:9000/tcp" # Server API
- "127.0.0.1:12201:12201/tcp" # GELF TCP
- "127.0.0.1:12201:12201/udp" # GELF UDP
networks:
- graylog
volumes:
- "graylog_data:/usr/share/graylog/data"
restart: "on-failure"
networks:
graylog:
driver: "bridge"
volumes:
mongodb_data:
mongodb_config:
graylog-datanode:
graylog_data:
⚠ Two things to adjust for a real server. First, the example binds every port to 127.0.0.1 — only reachable from the host. To accept logs and web traffic from other machines, drop the 127.0.0.1: prefix (and firewall accordingly), and set GRAYLOG_HTTP_EXTERNAL_URI to your server's address. Second, use named volume mounts as shown — bind mounts like ./data:/usr/share/graylog/data currently do not work correctly for the Graylog data directory.
06 — Start the Stack
Bring everything up
docker compose up -d
Watch startup (OpenSearch takes ~60-90s, Graylog another 30-60s)
docker compose logs -f
Verify all three containers are running
docker compose ps
NAME IMAGE STATUS
graylog-graylog-1 graylog/graylog:7.1 Up
graylog-datanode-1 graylog/graylog-datanode:7.1 Up
graylog-mongodb-1 mongo:7.0 Up
ℹ The Data Node is slow to become healthy on first boot. If the Graylog container restarts a couple of times while the Data Node finishes initialising, that is expected — give it a few minutes before concluding something is wrong.
07 — Preflight & First Login
With the Data Node architecture, Graylog first presents a preflight UI rather than going straight to the login page. This is where the Data Node provisions its certificates and the search backend is brought online. Open the web interface:
Web interface
http://your-server-ip:9000
Follow the preflight screen to provision and start the Data Node. Once it reports ready, the page hands off to the normal Graylog login. Sign in with username admin and the password whose SHA-256 you put in .env.
✓ The admin password is whatever you hashed in step 04 — there is no separate default to change here. But the admin account is the all-powerful root user, so keep that password strong and create individual, lower-privilege accounts under System / Users for day-to-day use.
⚠ The root admin password can only be changed by updating GRAYLOG_ROOT_PASSWORD_SHA2 and restarting — not through the UI. Don't lose it, and don't expose port 9000 to the internet without TLS and ideally a reverse proxy in front.
08 — Create an Input & Send a Log
Graylog receives nothing until you define an input. Go to System / Inputs, pick a type (e.g. Syslog UDP or GELF UDP), and launch it on a port that is actually mapped in the Compose file — an input bound to an unmapped port will never see traffic.
⚠ Inputs and port mappings must agree. If you launch a Syslog input on 5140, the container must publish 5140; the example file already maps 5140, 12201 (GELF), and 5044 (Beats). To use a different port, add it to the ports: list and recreate the container first.
Send a test syslog message (to a 5140/udp input)
echo "<34>$(date '+%b %e %T') $(hostname) testapp: hello graylog" \
| nc -u -w1 your-server-ip 5140
Open Search in the UI and the message should appear within a second or two. From here you point real log sources — rsyslog, Docker's GELF driver, Filebeat — at the matching input, then build streams, dashboards, and alerts on top.
09 — Key Commands
| Task | Command |
| Start stack | docker compose up -d |
| Stop stack | docker compose down |
| View logs (live) | docker compose logs -f graylog |
| Check status | docker compose ps |
| Check server throughput | docker compose exec graylog curl -s http://localhost:9000/api/system/throughput |
| List search indices | docker compose exec datanode curl -s http://localhost:9200/_cat/indices?v |
| Back up config (MongoDB) | docker compose exec -T mongodb mongodump --db graylog --archive | gzip > graylog-config.gz |
| Upgrade | docker compose pull && docker compose up -d |