DEV Community

Fernando Paladini
Fernando Paladini

Posted on

Self-Host Audio Stem Separation with Docker and Demucs

Turning a song into a vocal track, an instrumental, or separate drums and bass often leads to a choice between a hosted service and a complicated local machine setup. Hosted services can create privacy and upload concerns. A local setup can require Python, FFmpeg, PyTorch, model downloads, and a way to retrieve generated files.

This tutorial uses voice-separator-demucs, an MIT-licensed self-hosted application by Fernando Paladini. It provides a browser interface and a FastAPI backend for separating audio with Demucs. The Docker path keeps the model cache persistent and maps generated files to a directory on your computer.

The result is a local service at http://localhost:7860. You can upload an MP3, WAV, FLAC, M4A, or AAC file, choose stems such as vocals or drums, and download the generated MP3 files without sending the audio to a hosted application.

TL;DR

Clone the repository, start the Docker Compose service, open the local web interface, and upload an audio file. The current Compose file maps ./static/output to the container output directory and stores the model cache in a named Docker volume.

git clone https://github.com/paladini/voice-separator-demucs.git
cd voice-separator-demucs
docker compose up -d
Enter fullscreen mode Exit fullscreen mode

Open http://localhost:7860. The first separation downloads the selected model, so the initial run takes longer than later runs.

Prerequisites

You need:

  • Docker with the Compose plugin
  • A machine with enough disk space for Python dependencies and the Demucs model cache
  • An audio file you are allowed to process

The repository also documents a Python path that needs Python 3.8 or newer and FFmpeg. Docker is the more reproducible starting point because the Dockerfile installs FFmpeg and uses Python 3.9 inside the image. The Dockerfile currently installs the unpinned dependencies from requirements.txt, so this is a current-branch setup rather than a fully lockfile-reproducible build.

Start the local service

Clone the repository and start the service:

git clone https://github.com/paladini/voice-separator-demucs.git
cd voice-separator-demucs
docker compose up -d
Enter fullscreen mode Exit fullscreen mode

The Compose configuration exposes port 7860, mounts ./static/output at /app/static/output, and persists /root/.cache in a named volume called model-cache. The host output mount is useful because files created by the container remain available in the repository's static/output directory.

Check that the API is alive before uploading anything:

curl http://localhost:7860/health
Enter fullscreen mode Exit fullscreen mode

The application defines this endpoint as a simple health check. A successful response has the following shape:

{"status":"healthy","message":"Voice Separator API is running"}
Enter fullscreen mode Exit fullscreen mode

You can also open the interactive FastAPI documentation at http://localhost:7860/docs. The documented API includes /api/stems, /api/separate, and /api/separate-youtube.

Separate vocals with the API

The browser interface is the easiest path, but the API makes the workflow scriptable. The upload endpoint accepts a multipart file, a comma-separated stems value, and an optional model name. The default model is mdx_extra_q and the default stem is vocals.

The following command uses the repository's current endpoint and parameter names:

curl -X POST "http://localhost:7860/api/separate" \
  -F "file=@./example.mp3" \
  -F "stems=vocals"
Enter fullscreen mode Exit fullscreen mode

On success, the response includes success: true, the processed stems, an estimated processing time, and a file URL such as /static/output/vocals_<id>.mp3. Because the output directory is mounted by Compose, the same file is also available under static/output on the host.

To request more than one stem, pass a comma-separated list:

curl -X POST "http://localhost:7860/api/separate" \
  -F "file=@./example.wav" \
  -F "stems=vocals,instrumental"
Enter fullscreen mode Exit fullscreen mode

The available selections in the current implementation are drums, bass, other, vocals, and instrumental. The instrumental result combines drums, bass, and other. The backend validates both the model name and the selected stems before processing the file.

Understand the model choices

The interface exposes four model names: mdx_extra_q, mdx, htdemucs, and htdemucs_ft. The application selects CPU for mdx_extra_q and mdx when a GPU is unavailable. The two htdemucs variants require a CUDA-capable GPU in the current separator implementation.

For a first local test, keep the default mdx_extra_q model and request only vocals. The README describes it as the CPU-oriented default. It also notes that the first model download is about 200 MB, while exact disk usage and runtime depend on the dependency versions, hardware, audio length, and selected model.

Do not treat the README's rough processing times as a benchmark. They are operational guidance, not a guarantee. A longer file, several selected stems, or CPU-only processing can take substantially longer.

Why the Compose mounts matter

There are two different persistence concerns:

  1. The named model-cache volume prevents the model cache from disappearing when the container is recreated.
  2. The bind mount keeps generated audio in ./static/output on the host.

You can inspect the container and output files with:

docker ps --filter "name=voice-separator"
Get-ChildItem .\static\output
Enter fullscreen mode Exit fullscreen mode

The second command is for PowerShell. On macOS or Linux, use ls -lah static/output instead. If you use the single docker run command from the README without a bind mount, output files stay inside the container and must be copied out with docker cp.

When you are done, stop the service without deleting the cache:

docker compose down
Enter fullscreen mode Exit fullscreen mode

To remove the persistent model volume as well, use docker compose down -v. That forces a future startup to download the model again.

YouTube input and its boundary

The application also exposes /api/separate-youtube and uses yt-dlp to download audio before separation. The current route validates a YouTube URL and rejects videos longer than 10 minutes. It then removes the temporary downloaded audio after processing.

This feature does not remove copyright or platform obligations. Process only material you have permission to download and transform. A public URL is not automatically a license to copy its audio.

For a privacy-focused local workflow, prefer direct file upload. Both routes write output into the local application's static directory, and the app does not provide authentication in the current implementation. Do not expose port 7860 to the public internet without adding an access-control and deployment boundary appropriate for your environment.

Failure modes to check first

FFmpeg errors: The Docker image installs FFmpeg. If you use the Python path, install FFmpeg separately as described in the repository README and confirm that ffmpeg -version works.

Slow first request: Model loading and the initial model download happen before separation. Check the container logs with docker compose logs -f and wait for the model to finish loading.

Out of memory: Use a smaller input, select fewer stems, close competing workloads, or use the CPU-oriented default. GPU model choices are not a universal speed-up if the machine lacks the required GPU memory.

No files on the host: Confirm that you started with docker compose up -d from the repository directory and that static/output is the directory mounted by the Compose file. A plain docker run without -v keeps output inside the container.

Browser security warnings: The documented default is plain HTTP on localhost. The README includes an optional self-signed HTTPS example for local development. A self-signed certificate is not a production trust model, and the current application has no authentication layer.

Reproducible verification

After the health check, verify the core path in this order:

  1. Open /api/stems and confirm the available stem names.
  2. Upload a short audio file with stems=vocals.
  3. Confirm the response contains success: true and a generated output URL.
  4. Confirm a new MP3 appears in static/output.
  5. Play the output and compare its duration with the input.

The repository includes unit tests for mono-to-stereo tensor normalization. You can run the lightweight test module in a Python environment with its available dependencies:

python -m unittest tests/test_audio_tensor_utils.py -v
Enter fullscreen mode Exit fullscreen mode

The test suite does not prove that every model, codec, GPU, or long audio file works. It verifies a focused preprocessing behavior used by the separator.

FAQ

Does the audio leave my computer?

The Docker workflow runs the application locally. If you use the YouTube route, the app downloads the requested source first. Local execution does not make the application safe to expose publicly, because the current API has no authentication.

Can I run it without a GPU?

Yes, the current implementation selects CPU for mdx_extra_q and mdx. The htdemucs variants require a CUDA GPU according to the source code.

Is this a production-ready hosted service?

No claim like that is supported by the repository. Treat it as a local or controlled self-hosted application. Add authentication, resource limits, storage cleanup, and a deliberate reverse-proxy boundary before considering a shared deployment.

Takeaway

The useful pattern is not only the separation model. It is the local boundary around it: Docker packages the runtime, a named volume preserves expensive model downloads, and a host bind mount makes generated files easy to retrieve. Start with the default CPU-oriented model and a short file, verify the health and output paths, then decide whether your hardware and usage rights support larger jobs.

Have you found a reliable way to add authentication and per-job storage limits to a local audio-processing API without making the setup too complex?

AI assistance disclosure

AI assistance was used to organize this tutorial and review its wording. The commands, endpoint names, model names, file paths, limitations, and security notes were checked against the current public repository sources linked above. No performance benchmark or personal usage claim is implied.

Top comments (0)