Running Lavalink in Docker With Docker Compose
Run Lavalink v4 in Docker with Docker Compose — the official image, mounting application.yml and plugins, memory flags, networking with your bot and safe updates.
On this page
Docker is a tidy way to run Lavalink: the official image bundles the right Java version, updates are a single command, and your configuration lives in two files you can keep in git. This guide sets up Lavalink v4 with Docker Compose on a Linux server.
Prerequisites
- A Linux server with Docker and the Compose plugin installed. On a Kerit Cloud VPS or self-managed Lavalink plan, you have root access to install them — see installing Docker and Docker Compose.
- A working
application.yml(see application.yml explained).
Folder layout
/opt/lavalink/
├── docker-compose.yml
├── application.yml
└── plugins/
Create it:
sudo mkdir -p /opt/lavalink/plugins
cd /opt/lavalink
docker-compose.yml
services:
lavalink:
image: ghcr.io/lavalink-devs/lavalink:4
container_name: lavalink
restart: unless-stopped
environment:
- _JAVA_OPTIONS=-Xmx700M -XX:+UseG1GC -XX:MaxGCPauseMillis=50
volumes:
- ./application.yml:/opt/Lavalink/application.yml:ro
- ./plugins:/opt/Lavalink/plugins
ports:
- "2333:2333"
mem_limit: 1g
What each part does:
image: ghcr.io/lavalink-devs/lavalink:4— the official image, tracking the latest v4 release. Pin a specific version tag (for example4.x.y) if you prefer controlled upgrades.restart: unless-stopped— Docker restarts Lavalink if it crashes and when the server reboots._JAVA_OPTIONS— JVM flags. Keep-Xmxat about 70–75% of the container’s memory limit. See JVM tuning for Lavalink.- Volumes — your
application.ymlis mounted read-only into the path Lavalink reads, and thepluginsfolder persists downloaded plugins between container restarts. mem_limit— caps the container’s memory so Lavalink can’t starve other services on the same server.
Plugin folder permissions
The official image runs Lavalink as a non-root user. That user must be able to write to the mounted plugins folder, or plugin downloads fail at startup with permission errors. The Lavalink documentation lists the user ID the image uses; set ownership of ./plugins to match (with chown) before the first start.
Start it
docker compose up -d
docker compose logs -f lavalink
Wait for “Lavalink is ready to accept connections.” On first start, Lavalink downloads plugins from lavalink.plugins into the plugins folder.
Test from the server:
curl -H "Authorization: your-password" http://localhost:2333/version
Configuring with environment variables
Lavalink is a Spring Boot application, so settings in application.yml can also be supplied as environment variables using relaxed binding — nested keys become upper-case with underscores:
environment:
- SERVER_PORT=2333
- LAVALINK_SERVER_PASSWORD=${LAVALINK_PASSWORD}
This keeps the password out of application.yml, so the config file can live in git. Put the real value in a .env file next to docker-compose.yml (Compose reads it automatically) and keep that file out of version control.
Running the bot alongside Lavalink
If your bot runs on the same server in Docker, put both services in one Compose file. They share a network, and the bot reaches Lavalink by service name:
services:
lavalink:
image: ghcr.io/lavalink-devs/lavalink:4
restart: unless-stopped
environment:
- _JAVA_OPTIONS=-Xmx700M
- LAVALINK_SERVER_PASSWORD=${LAVALINK_PASSWORD}
volumes:
- ./application.yml:/opt/Lavalink/application.yml:ro
- ./plugins:/opt/Lavalink/plugins
# no "ports:" — only reachable from other containers on this network
bot:
build: ./bot
restart: unless-stopped
depends_on:
- lavalink
environment:
- DISCORD_TOKEN=${DISCORD_TOKEN}
- LAVALINK_HOST=lavalink
- LAVALINK_PORT=2333
- LAVALINK_PASSWORD=${LAVALINK_PASSWORD}
Leaving out the ports section for Lavalink means it isn’t exposed to the internet at all — the most secure option when the bot is on the same machine. depends_on starts Lavalink first, but it doesn’t wait for it to be ready, so make sure your bot’s client retries the connection (all major clients do).
Updating
With the :4 tag:
docker compose pull
docker compose up -d
Compose recreates the container with the new image; your config and plugins stay in the mounted folders. To update a plugin, change its version in application.yml and restart:
docker compose restart lavalink
Plugins update far more often than Lavalink itself — especially the YouTube plugin — so this is the command you’ll run most. See the YouTube source plugin.
If you run several nodes, update them one at a time so players can resume or move to a healthy node.
Logs and troubleshooting
docker compose logs --tail=200 lavalink # recent logs
docker stats lavalink # live CPU and memory
docker compose exec lavalink java -version # confirm the Java version inside
Common issues:
- Plugins fail to download — permissions on the
pluginsfolder, or no outbound internet from the container. - Config changes ignored — the volume path is wrong. The file must be mounted at
/opt/Lavalink/application.yml. - Container keeps restarting — check logs for YAML errors, or an out-of-memory kill if
-Xmxis too close tomem_limit. - Bot can’t connect — from another server, check
portsand your firewall; from the same Compose file, use the service name (lavalink), notlocalhost.
More fixes in troubleshooting Lavalink.
Docker or systemd?
Both are fine. Docker makes Java version management and updates trivial and keeps everything in two files; systemd has fewer layers and uses slightly less memory. If you already run your bot or other services in Docker, keep Lavalink there too. For the systemd route, see setting up Lavalink v4 from scratch.
Summary
Run Lavalink in Docker with the official ghcr.io/lavalink-devs/lavalink:4 image, mount application.yml read-only and a writable plugins folder, set heap flags through _JAVA_OPTIONS with a matching memory limit, and use restart: unless-stopped. Keep secrets in environment variables, put the bot on the same Compose network without exposing Lavalink’s port when they share a server, and update with docker compose pull && docker compose up -d.