All apps · 0 apps
DizzyBot
Docker app from John's Repository
Overview
Readme
View on GitHubDizzyBot
DizzyBot is a fully open-source, self-hosted Discord music bot written in Python. It uses Lavalink for voice playback and is designed for Docker hosts, including Unraid.
The project is deliberately easy to fork. Queueing, source resolution, playback, interactive controls, permissions, settings, presentation, commands, health checks, and runtime lifecycle all have public base classes and first-party default subclasses. A single composition module selects the implementations.
Features
- YouTube videos and playlists
- YouTube Music links (through the YouTube source)
- SoundCloud tracks and sets
- Spotify track, album, and playlist links mirrored to YouTube or SoundCloud
- Apple Music tracks, albums, and playlists mirrored to YouTube or SoundCloud
- Optional TIDAL tracks, albums, and playlists mirrored to YouTube or SoundCloud
- Bandcamp tracks and albums
- Persistent per-server internet radio stations, with no stations imposed by default
- Live radio metadata in the playback panel when a station publishes it
- Slash commands only; no message-content intent
- Independent queues and playback in multiple Discord servers
- Pause, resume, seek, volume, queue editing, shuffle, and track/queue repeat
- An automatically updated Now Playing panel with Previous, Play/Pause, Skip, and Stop buttons
- SQLite-backed per-server settings with automatic migrations
- Configurable same-channel permissions plus administrator/DJ-role overrides
- Configurable idle and empty-channel disconnects, with DJ-controlled 24/7 mode
- Docker health endpoints and pinned Lavalink plugins
- One-container Unraid template with bundled private Lavalink
- Published
linux/amd64andlinux/arm64image workflow
Spotify, Apple Music, and TIDAL are metadata sources rather than audio sources. DizzyBot uses LavaSrc mirroring: the selected service supplies metadata and a playable match is found on YouTube or SoundCloud. Bandcamp, YouTube, SoundCloud, and saved radio streams are relayed directly by Lavalink. Operators must follow the terms of every configured media service. DizzyBot does not bypass DRM, download media, or cache audio.
Unraid: one-container installation
Unraid users do not need to clone the repository, manage Compose, create a Lavalink container, or
publish any ports. The Community Applications template deploys
ghcr.io/john-9474/dizzybot-standalone:latest, which contains both DizzyBot and its private Lavalink
process.
Once the Community Applications listing is published:
- Open Apps in Unraid and search for DizzyBot.
- Select Install and enter the Discord bot token. Spotify and TIDAL credentials are optional; Apple Music and Bandcamp work without additional credentials.
- Select Apply. The template creates the appdata mapping and starts the single container.
The Unraid form exposes every user-facing bot default and deployment-wide limit under Show more
settings. Unraid turns those fields directly into container environment variables; it does not
read this repository's .env file and does not require a custom YAML file.
The standalone image generates its internal Lavalink password automatically. It runs the bot and
Lavalink as Unraid's standard UID/GID 99:100, persists server settings under
/mnt/user/appdata/dizzybot, supervises both processes, and stops the whole container if either one
fails. The source for the listing is templates/dizzybot.xml.
Unraid stores an installed app's form as a local template. Consequently, newly added fields may not
appear when only the container image is updated. Existing installations can add the documented
environment variable manually, or install from a fresh Community Applications template while
retaining the /mnt/user/appdata/dizzybot mapping and its database.
The template and image publication workflow are included in this repository. The listing will become searchable after a tagged image has been published to the public GHCR package and the template has passed the Community Applications Validate and Scan submission.
Quick start with Docker Compose
Prerequisites:
- Docker Engine with Docker Compose v2
- A Discord application and bot token
- Optional Spotify developer application for Spotify links
- Optional LavaSrc-compatible TIDAL token for TIDAL links and searches
In the Discord Developer Portal, create an application and bot. You do not need to enable Message Content Intent.
Generate an invite under OAuth2 > URL Generator with the
botandapplications.commandsscopes. GrantView Channels,Send Messages,Embed Links,Connect,Speak, andUse Slash Commands.Clone this repository, copy the environment template, and edit
.env:cp .env.example .envSet
DISCORD_TOKENto the bot token andLAVALINK_PASSWORDto a long random value. The same Lavalink password is passed to both containers automatically. The repository ignores.env, but you should still restrict access to it and never commit or share it. This.envfile is read by Docker Compose for variable substitution; DizzyBot does not search for or load.envfiles.Start both containers:
docker compose up --detach --build docker compose ps
The bot and Lavalink communicate over a private Compose network. Lavalink's port and the bot's health port are not published to the host by default.
For released images, set DIZZYBOT_VERSION in .env, then run docker compose pull followed by
docker compose up --detach. The Compose file points to
ghcr.io/john-9474/dizzybot and ghcr.io/john-9474/dizzybot-lavalink.
YouTube setup
YouTube playback uses the yt-dlp source built into LavaSrc. The image bundles pinned yt-dlp and Deno
executables for both linux/amd64 and linux/arm64, so no Google account, cookies, converter site, or
extra container is required. yt-dlp resolves the playable stream and Lavalink relays it directly to
Discord; DizzyBot does not save or cache media files.
YouTube changes its playback interface frequently. DizzyBot therefore pins yt-dlp so image builds are reproducible, while subsequent DizzyBot releases can update the extractor independently of Lavalink. Occasional videos may still be unavailable because of regional, age, account, or rights restrictions.
Music sources
YouTube, YouTube Music, SoundCloud, Apple Music, and Bandcamp are available without provider
credentials. Apple Music is a metadata source: the bundled LavaSrc plugin obtains its public media
API token and mirrors matches to YouTube or SoundCloud. Set APPLE_MUSIC_COUNTRY_CODE to a
two-letter storefront code if the default GB is not appropriate. Bandcamp public tracks and albums
are loaded directly by Lavalink.
For /play searches, source can be auto, youtube, soundcloud, spotify, apple_music,
tidal, or bandcamp. You can also choose any configured source as a server's default with
/settings search-provider. A URL automatically selects its matching source regardless of that
default.
Spotify
Create an application in the Spotify Developer Dashboard
and add these values to .env:
SPOTIFY_CLIENT_ID=your-client-id
SPOTIFY_CLIENT_SECRET=your-client-secret
SPOTIFY_COUNTRY_CODE=GB
Restart the stack after changing these values. Spotify is enabled automatically when both the client ID and client secret are present. It is optional: without both credentials, YouTube and SoundCloud remain available and Spotify requests return a configuration error. Current Spotify development-mode accounts require the application owner to have Premium and are subject to Spotify's user and quota limits.
TIDAL
TIDAL support is optional because LavaSrc requires a compatible API token. Add it to .env or the
TIDAL Token field in the Unraid template:
TIDAL_TOKEN=your-lavasrc-compatible-token
TIDAL_COUNTRY_CODE=GB
Restart the container after changing it. TIDAL activates automatically when the token is non-empty; without one, its commands return an actionable configuration error and all other sources continue to work. DizzyBot does not collect TIDAL login details or browser session cookies, and the project does not issue provider tokens.
The provided Compose stack reads all credentials from .env. Both containers also understand
*_FILE variants for custom deployments that mount Docker secrets instead; supported names include
DISCORD_TOKEN_FILE, LAVALINK_PASSWORD_FILE, SPOTIFY_CLIENT_ID_FILE, and
SPOTIFY_CLIENT_SECRET_FILE, and TIDAL_TOKEN_FILE.
Amazon Music and Pandora are not supported by the pinned Lavalink plugins. Deezer, Qobuz, Yandex Music, VK Music, and JioSaavn are not enabled because their LavaSrc integrations play directly and may require private account/session credentials, decryption material, or region-specific setup. They can be evaluated separately in a fork without weakening the default public image.
Internet radio
DizzyBot can relay direct internet radio streams through Lavalink. It deliberately ships with an empty station list: each Discord server chooses and names its own stations. For example, a DJ or administrator can save a Radio.co listen endpoint with:
/radio add name:House Nation url:https://streaming.radio.co/s06bd9d805/listen
Then any member who meets the normal voice-channel rules can use /radio play name:House Nation.
Both modern HTTPS streams and older HTTP Icecast/SHOUTcast streams are supported. Use the direct
audio endpoint—usually returning a content type such as audio/mpeg—rather than a station's web
player page. Plain HTTP works for legacy stations but is not encrypted in transit.
For Icecast and SHOUTcast streams that publish ICY StreamTitle metadata, the playback panel adds
an On air field and refreshes it when the station changes its current track or programme. Radio
metadata is optional: streams that omit it continue playing with the normal station-only panel.
Absolute Radio and other Bauer stations are also supported through their public now-playing feed,
which avoids the blank metadata caused by personalised adverts at the start of those streams.
Adding and removing stations requires an administrator or the configured DJ role. This is important
because the saved URL causes the self-hosted audio service to make an outbound connection. URLs with
embedded credentials and URLs resolving to local, private, link-local, or reserved addresses are
blocked by default. A trusted private deployment can opt in with
bot.allow_private_radio_streams: true. Radio streams remain subject to the configured idle/empty
channel timeout and 24/7 mode. If an upstream live stream times out or ends unexpectedly, DizzyBot
reloads its media URL and attempts to reconnect up to three times before continuing with the queue.
Commands
| Command | Behavior |
|---|---|
/play query [source] |
Search or queue a supported track/playlist URL; joins automatically |
/join, /leave |
Join, or stop/clear/disconnect |
/pause, /resume, /skip, /stop |
Control current playback |
/queue [page], /nowplaying |
Inspect playback and the upcoming queue |
/remove position, /move from_position to_position |
Edit upcoming tracks |
/clear, /shuffle |
Clear or shuffle upcoming tracks |
/repeat mode |
Select off, track, or queue |
/volume percent |
Set session volume from 0–100 |
/seek position |
Seek using seconds, MM:SS, or HH:MM:SS |
/radio add name url |
Save a direct stream URL (DJ role or administrator) |
/radio play name |
Play or queue a saved station; station names autocomplete |
/radio playrandom |
Play or queue a randomly selected saved station |
/radio list |
List saved stations with Previous and Next page buttons |
/radio remove name |
Remove a saved station (DJ role or administrator) |
/settings show |
Display persistent settings (Manage Server required) |
/settings volume |
Change the default volume |
/settings idle-timeout |
Change empty/idle voice disconnect time from 30–86400 seconds |
/settings 24-7 |
Enable or disable persistent voice connection (DJ role or administrator) |
/settings dj-role |
Set the role allowed to control an active session remotely |
/settings search-provider |
Select the default provider for text searches |
/settings reset |
Restore all deployment defaults |
Normal members must be in the bot's voice channel. Administrators and the configured DJ role may control an existing session remotely, but no command silently moves an active player to another voice channel.
When playback starts, DizzyBot posts one Now Playing panel in the active announcement channel and
edits that message as the session changes. It shows the title, artist, artwork when available,
playback time, requester, source, volume, repeat mode, current queue position, and the playlist name
and playlist position when applicable. The buttons use the same voice-channel and DJ/administrator
rules as slash commands. Previous returns to the last successfully played or manually skipped track;
failed tracks are excluded from history. Panels are disabled when playback stops or the bot leaves.
By default, a public command response deletes and reposts the active panel so it remains the latest
bot message in its channel. Set bot.repost_player_controls: false (or the corresponding advanced
Unraid option) to keep editing the original panel in place instead.
By default, DizzyBot leaves five minutes after playback becomes idle or the voice channel has no
human listeners. Administrators can change that delay with /settings idle-timeout. Members with the
configured DJ role can use /settings 24-7 enabled:true to suppress automatic departure, or
enabled:false to restore it. Administrators can also toggle 24/7 mode as an emergency override.
24/7 mode keeps an active connection open; it does not restore a voice session after a bot restart.
Configuration
There are three deployment interfaces and one persistent per-server layer:
| Interface | Intended use | How values reach DizzyBot |
|---|---|---|
| Unraid template fields | Normal one-container Unraid installations | Unraid creates container environment variables |
.env |
The supplied Docker Compose stack only | Compose substitutes values into compose.yaml, which passes them to the containers |
config.yml |
Advanced/custom Docker deployments and forks | DizzyBot reads the mounted YAML file directly |
/settings commands |
Individual Discord-server preferences | Values are persisted in /data/dizzybot.sqlite3 |
config.example.yml is the canonical structured reference and contains every
ordinary application setting, including internal defaults used by the packaged images. A custom
deployment can copy it and mount the result at /etc/dizzybot/config.yml:ro. Normal Unraid users do
not need this mount, and .env is not used by Unraid.
The Unraid template and Compose .env.example expose the user-relevant bot options: default volume,
idle timeout, default 24/7 mode, default search source, playlist/queue/radio limits, private radio
access, and player-panel reposting. Database, health, and private Lavalink connection values remain
internal in the standalone image because changing them would conflict with its volume, health check,
or bundled audio process.
Environment variables beginning with DIZZYBOT__ override nested YAML keys. For example:
DIZZYBOT__BOT__DEFAULT_VOLUME=60
DIZZYBOT__BOT__PLAYLIST_TRACK_LIMIT=200
DIZZYBOT__BOT__RADIO_STATION_LIMIT=25
DIZZYBOT__BOT__REPOST_PLAYER_CONTROLS=false
DIZZYBOT__BOT__COMMAND_SYNC_GUILD_ID=123456789012345678
DIZZYBOT__HEALTH__PORT=8081
Configuration precedence is DIZZYBOT__... environment override, direct credential or credential
file, YAML, then built-in default. Playlist and total queue limits must be between 1 and 500. A
development guild ID makes slash commands sync immediately to that guild; leave it as null in
production to use global commands.
Default volume, idle timeout, 24/7 mode, and search source are starting values for each Discord
server. Once changed through /settings, the server's SQLite value takes precedence over the
deployment default. /settings reset restores the currently configured deployment defaults. Queue,
playlist-import, and saved-radio limits remain deployment-wide caps.
Persistent server settings and saved radio stations live in /data/dizzybot.sqlite3. Queues, voice
sessions, repeat state, and session volume intentionally do not survive restarts. Back up the /data
volume before upgrades; database migrations run automatically before Discord connects. The default
radio station limit is 50 per server and can be configured from 1 to 100.
Health endpoints are:
/health/live: the process and HTTP server are running/health/ready: storage is migrated and Discord and Lavalink are connected
Customizing a fork
Public contracts are in src/dizzybot/contracts, default behavior is in
src/dizzybot/defaults, and composition.py is the only intended
class-selection point.
For example, a fork can reverse every batch added to a queue:
from dizzybot.defaults.queue import DefaultQueue
from dizzybot.domain import Track
class ReverseQueue(DefaultQueue):
def enqueue(self, tracks: tuple[Track, ...]) -> None:
super().enqueue(tuple(reversed(tracks)))
Then change only the queue factory in build_services:
players = DefaultPlayerManager(
audio,
settings,
presenter,
player_factory=DefaultGuildPlayer,
queue_factory=ReverseQueue,
queue_limit=config.bot.queue_track_limit,
)
The same pattern applies to BaseAudioBackend, BaseTrackResolver, BaseGuildPlayer,
BasePermissionPolicy, BaseSettingsRepository, BasePresenter, command bases, the health service,
BasePlaybackControls, BaseRadioRepository, BaseRadioResolver,
BaseRadioMetadataProvider, and runtime. There is
intentionally no dynamic plugin loader or import-path configuration.
Development
Python 3.13 and uv are recommended:
uv sync --frozen --extra dev
uv run ruff format --check src tests
uv run ruff check src tests
uv run pyright
uv run pytest
uv run pip-audit
Validate a configuration without connecting to Discord:
DISCORD_TOKEN=dummy LAVALINK_PASSWORD=dummy \
uv run dizzybot --config config.example.yml --check-config
Live provider tests are deliberately not part of required CI because upstream search results and availability are nondeterministic. Unit and integration tests use recorded payloads and fake service boundaries. Pull requests build the bot-only, Lavalink, and standalone containers; version tags publish all three as multi-architecture GHCR images with provenance and an SBOM.
License
DizzyBot is licensed under GNU GPL version 3. Lavalink and its plugins are separate projects distributed under their respective licenses.
Install DizzyBot on Unraid in a few clicks.
Find DizzyBot in Community Apps on your Unraid server, review the template, and click Install. Unraid handles the Docker app or plugin setup from the published template.
Requirements
Categories
Related apps
Explore more like this
Explore allDetails
ghcr.io/john-9474/dizzybot-standalone:latestRuntime arguments
- Network
bridge- Shell
sh- Privileged
- false
- Extra Params
--restart=unless-stopped
Template configuration
Persistent DizzyBot database and server settings.
- Target
- /data
- Default
- /mnt/user/appdata/dizzybot
Required token from the Discord Developer Portal.
- Target
- DISCORD_TOKEN
Optional. Spotify activates automatically when both Spotify credentials are provided.
- Target
- SPOTIFY_CLIENT_ID
Optional. Spotify activates automatically when both Spotify credentials are provided.
- Target
- SPOTIFY_CLIENT_SECRET
Two-letter country code used for Spotify metadata.
- Target
- SPOTIFY_COUNTRY_CODE
- Default
- GB
Two-letter storefront country code used for Apple Music metadata. No Apple credential is required.
- Target
- APPLE_MUSIC_COUNTRY_CODE
- Default
- GB
Optional TIDAL API token. TIDAL activates automatically when provided.
- Target
- TIDAL_TOKEN
Two-letter country code used for TIDAL metadata.
- Target
- TIDAL_COUNTRY_CODE
- Default
- GB
Python bot log level.
- Target
- DIZZYBOT__LOGGING__LEVEL
- Default
- INFO
Default volume for a server until changed with /settings volume. Valid range: 0-100.
- Target
- DIZZYBOT__BOT__DEFAULT_VOLUME
- Default
- 75
Default seconds before leaving an idle or empty voice channel. Valid range: 30-86400; servers can override it with /settings idle-timeout.
- Target
- DIZZYBOT__BOT__IDLE_TIMEOUT_SECONDS
- Default
- 300
Default persistent voice mode for servers until changed with /settings 24-7.
- Target
- DIZZYBOT__BOT__STAY_CONNECTED
- Default
- false
Default text-search source until changed with /settings search-provider: youtube, soundcloud, spotify, apple_music, tidal, or bandcamp.
- Target
- DIZZYBOT__BOT__DEFAULT_SEARCH_SOURCE
- Default
- youtube
Maximum tracks imported from one playlist. Valid range: 1-500.
- Target
- DIZZYBOT__BOT__PLAYLIST_TRACK_LIMIT
- Default
- 100
Maximum current and upcoming tracks in each Discord server queue. Valid range: 1-500.
- Target
- DIZZYBOT__BOT__QUEUE_TRACK_LIMIT
- Default
- 500
Maximum number of saved radio stations per Discord server.
- Target
- DIZZYBOT__BOT__RADIO_STATION_LIMIT
- Default
- 50
Allow saved radio URLs to reach private or local network addresses. Enable only for a trusted server.
- Target
- DIZZYBOT__BOT__ALLOW_PRIVATE_RADIO_STREAMS
- Default
- false
Delete and repost the active Now Playing controls after public command responses so they remain the latest bot message.
- Target
- DIZZYBOT__BOT__REPOST_PLAYER_CONTROLS
- Default
- true
Development only. Discord server ID for immediate guild command sync; leave as null for global production commands.
- Target
- DIZZYBOT__BOT__COMMAND_SYNC_GUILD_ID
- Default
- null
Memory options for the bundled Lavalink JVM.
- Target
- _JAVA_OPTIONS
- Default
- -Xms128m -Xmx1g
UID used for the bot and Lavalink processes. The Unraid default is 99.
- Target
- PUID
- Default
- 99
GID used for the bot and Lavalink processes. The Unraid default is 100.
- Target
- PGID
- Default
- 100