Automated Plex trash management tool that validates filesystem state before emptying library trash. Prevents accidental deletion when using network-mounted storage or other unreliable forms of storage.
mkdir dumpsterr && cd dumpsterr
curl -fsSL https://raw.githubusercontent.com/chase-roohms/dumpsterr/refs/heads/main/docker-compose/quickstart.sh | bash
# Edit config.yml and .env
docker compose up -dWhen Plex runs on a different host than your media storage (NFS, SMB, etc.), network interruptions can cause mount failures or timeouts. If mounts go down mid Plex scan, some media is marked as deleted and the metadata is removed from your library. When mounts are fully available again a rescan and metadata rebuild is triggered which fills your "Recently Added" with movies and shows you have had for months.
Examples of Reddit user's posts who faced this problem:
The suggested "fix" for this is always to disable "Empty trash automatically after every scan" - which then means you have to periodically empty your trash manually to avoid all the little red trash symbols on intentionally deleted media, and the scary red "unavailable" buttons on upgraded files.
Dumpsterr validates filesystem state before allowing Plex to empty trash:
- Checks directory accessibility
- Verifies minimum file counts
- Follows symlinks (see details)
- Confirms file count thresholds are within an acceptable range:
if (files on disk / media in library) > minimum threshold in config - Verifies the library is not currently being scanned
- Only empties trash when all validations pass for a library
- Plex Media Server with API access
- Docker (or Python 3.12+)
- Read access to media directories
Create data/config.yml:
libraries:
- name: Movies # Plex library name
path: /media/movies/ # Container path to media
min_files: 100 # Minimum files required
min_threshold: 90 # Minimum percentage of expected files
- name: TV Shows
path:
- /media/shows/
- /media/more-shows/
min_files: 50
min_threshold: 90
settings:
log_level: INFO # DEBUG, INFO, WARNING, ERROR, CRITICALConfiguration validation:
- Schema: schemas/config.schema.yml
- Validated on startup using jsonschema
Required:
PLEX_URL- Plex server URL (e.g.,http://192.168.1.100:32400)PLEX_TOKEN- Get your token
Optional:
TZ- Timezone (e.g.,America/New_York)CRON_SCHEDULE- Cron schedule (default:0 * * * *- hourly)LOG_FORMAT- Log format:standardorjson(default:standard, case-insensitive)LOG_FILE- Optional file path for log rotation (e.g.,/app/logs/dumpsterr.log)
Note: Metrics collection is automatic but optional. Mount /app/metrics to persist metrics history.
The container automatically handles directory creation and permissions.
services:
dumpsterr:
image: neonvariant/dumpsterr:latest
container_name: dumpsterr
volumes:
- ./config.yml:/app/data/config.yml:ro
- ./metrics:/app/metrics # For metrics persistence (optional)
- /path/to/movies:/media/movies:ro
- /path/to/shows:/media/shows:ro
environment:
- PLEX_URL=${PLEX_URL}
- PLEX_TOKEN=${PLEX_TOKEN}
- TZ=${TZ}
- LOG_FORMAT=standard # or 'json' for structured logging
# Recommended: Configure log rotation to prevent unbounded growth
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
restart: unless-stopped.env
PLEX_URL=http://<IP>:<PORT>
PLEX_TOKEN=PLEX-TOKEN-HERE
TZ=Time/ZoneRun:
docker compose up -ddocker run -d \
--name dumpsterr \
-v ./config.yml:/app/data/config.yml:ro \
-v ./metrics:/app/metrics \
-v /path/to/movies:/media/movies:ro \
-v /path/to/shows:/media/shows:ro \
-e PLEX_URL=http://192.168.1.100:32400 \
-e PLEX_TOKEN=your_token_here \
-e TZ=America/New_York \
--log-opt max-size=10m \
--log-opt max-file=3 \
--restart unless-stopped \
neonvariant/dumpsterr:latestAutomatically collected in metrics/metrics.json:
- Run history (last 100 runs)
- Success/failure rates
- Per-library statistics
- Processing duration
See docs/OBSERVABILITY.md for details.
Two formats available:
- Standard (default): Human-readable
- JSON: Structured for log aggregation
settings.log_level and LOG_FORMAT are case-insensitive. Invalid values fall back to INFO and standard.
Configure log rotation via Docker logging driver (recommended) or file-based rotation.
See docs/OBSERVABILITY.md for configuration examples.
0: All libraries processed successfully1: Partial failure (some libraries failed)2: Complete failure (all libraries failed)
git clone https://github.com/chase-roohms/dumpsterr.git
cd dumpsterr
docker build -t dumpsterr .Runs hourly by default. Schedule is controlled via the CRON_SCHEDULE environment variable. Also executes on container startup.
To modify schedule, set CRON_SCHEDULE in your docker-compose.yml or .env file:
# docker-compose.yml
environment:
- CRON_SCHEDULE=0 * * * * # Hourly (default)
# - CRON_SCHEDULE=0 */6 * * * # Every 6 hours
# - CRON_SCHEDULE=0 0 * * * # Daily at midnightOr in your .env file:
CRON_SCHEDULE=0 */6 * * * # Every 6 hours- Directory accessibility check
- Minimum file count verification
- Plex library size comparison
- Threshold percentage validation (current files / expected files > minimum threshold)
- Trash emptying (only if all checks pass)
Validation failure skips library without emptying trash.
View logs:
docker logs dumpsterr
docker logs -f dumpsterr # Follow modeLog levels: DEBUG, INFO (default), WARNING, ERROR, CRITICAL
Error: IsADirectoryError: [Errno 21] Is a directory: 'data/config.yml'
Cause: The config file doesn't exist on your host system. When Docker tries to mount a non-existent file, it creates a directory instead.
Solution:
- Ensure the config file exists on your host at the path specified in your
docker-compose.yml - If using the quickstart, the config file should be in the same directory as your
docker-compose.yml - Recreate the container to remount the volume correctly:
docker compose down docker compose up -d
Example: If your docker-compose.yml has:
volumes:
- ./config.yml:/app/data/config.yml:roThen config.yml must exist in the same directory as docker-compose.yml before running docker compose up.
If your media library contains symlinks (e.g., symlinks pointing to an rclone mount), both the symlink source and target directories must be mounted in the container.
How it works: Dumpsterr follows symlinks to their target paths and validates that the target files exist. If symlinks point to unmounted paths, they're detected as broken, causing validation to fail (which prevents trash emptying when your mount is down ✅).
Example Configuration:
volumes:
- /host/media:/media:ro # Your organized symlink library
- /mnt/rclone:/mnt/rclone:ro # The actual files symlinks point toImportant: The mount paths inside the container must match the paths your symlinks reference. If your symlinks point to /mnt/rclone/Movies/file.mkv, then /mnt/rclone must be mounted in the container at that exact path.
Relative symlinks: If your symlinks use relative paths (e.g., ../rclone/file.mkv), ensure the relative directory structure is preserved in your container mounts.
Validation behavior with broken symlinks:
- Mount is up → symlinks resolve → files counted normally
- Mount is down → symlinks broken → file count drops → validation fails → trash not emptied ✅
Check that:
PLEX_URLis accessible from the containerPLEX_TOKENis valid- Media paths in
config.ymlmatch the container paths (not host paths) - If using symlinks, ensure both source and target directories are mounted
Disable "Empty trash automatically after every scan" in:
- Settings > Library
This lets Dumpsterr control trash emptying on its schedule.
src/
├── main.py # Validation and orchestration
├── config/ # Configuration loading and validation
├── filesystem/ # Directory and file count checks
├── plex_client/ # Plex API interaction
└── public/ # JSON schema for config validation
- PyYAML
- jsonschema
- requests
Dumpsterr includes a comprehensive test suite covering all modules.
pip install -r requirements.txt# Run all tests
pytest
# Run with coverage report
pytest --cov=src --cov-report=html
# Run specific test files
pytest tests/test_config.py
pytest tests/test_filesystem.py
pytest tests/test_plex_client.py
pytest tests/test_main.py
# Skip Plex integration tests (require real server)
pytest -m "not plex"Some tests require a real Plex server connection. To enable these:
-
Copy the environment template:
cp .env.test.example .env.test
-
Edit
.env.testwith your Plex server details:PLEX_URL=http://your-plex-server:32400 PLEX_TOKEN=your_actual_plex_token -
Run Plex integration tests:
pytest -m plex
The test suite includes:
- Configuration Tests: YAML parsing, schema validation, error handling
- Filesystem Tests: Directory validation, file counting, permissions
- Plex Client Tests: API mocking, error handling, retry logic
- Integration Tests: Complete workflow testing with mocked dependencies
View detailed testing documentation: docs/TESTS.md
Dumpsterr is maintained under an MIT license, see the LICENSE file for details.
