LastFM automates weekly posting of your Last.fm Weekly Artist Chart to Bluesky (AT Protocol) and Mastodon.
🌐 Public Instance: https://lastfm.butialabs.com
- Create a
compose.yml:
services:
lastfm:
image: ghcr.io/butialabs/lastfm:latest
container_name: lastfm
environment:
TZ: UTC
PHP_DATE_TIMEZONE: UTC
APP_URL: https://your-domain.com
APP_KEY: base64:your_app_key # optional, auto-generated on first boot
LASTFM_API: your_lastfm_api_key
ADMIN_USER: admin
ADMIN_PASSWORD: your_secure_password
ports:
- 80:8080
volumes:
- lastfm-data:/app/data
restart: unless-stopped
volumes:
lastfm-data:- Start the container:
docker compose up -d- Access the application at
http://localhost(or your configured domain). The admin panel is at/admin.
| Variable | Description | Required |
|---|---|---|
APP_URL |
Public URL of your instance | Yes |
LASTFM_API |
Your Last.fm API key (Get one here) | Yes |
ADMIN_USER |
Initial admin username (seeded on first boot) | First boot |
ADMIN_PASSWORD |
Initial admin password (seeded on first boot, stored hashed) | First boot |
APP_KEY |
Laravel encryption key (php artisan key:generate --show). Auto-generated and persisted in the data volume if omitted |
No |
ENCRYPTION_KEY |
Upgrades from v1 only: legacy key used to decrypt old credentials during the one-time import. Can be removed afterwards | No |
TZ / PHP_DATE_TIMEZONE |
Container and PHP timezone (e.g., America/Sao_Paulo). Schedules are always stored and compared in UTC |
No |
LASTFM_PROXY_URL |
Proxy fallback for Last.fm image scraping (see below) | No |
THEAUDIODB_API_KEY / FANART_API_KEY |
Alternative artist image providers for the admin panel | No |
MAX_ERROR_COUNT |
Send attempts before giving up until next week (default 3) |
No |
BLUESKY_MENTION / MASTODON_MENTION |
Account mentioned in generated posts (defaults to the Butiá Labs accounts) | No |
IMAGE_PLACEHOLDER_RETRY_DAYS |
Days before a placeholder (failed or stub download) is attempted again, when the artist next shows up in a chart (default 30) |
No |
The base image's own variables (PHP_OPCACHE_*, NGINX_*, SSL_MODE, AUTORUN_*, ...) are documented in the serversideup/php reference.
APP_KEY stability: encrypted credentials (Bluesky app passwords / Mastodon tokens) are tied to
APP_KEY. Once set, by you or auto-generated intodata/.app_key, never change it, or users will need to log in again.
Keep ENCRYPTION_KEY (the old 32-char key) set on the first boot of the new version.
After a successful import, ENCRYPTION_KEY can be removed from the environment.
In 2019, Last.fm removed the image API, so artist images have to be scraped from the public artist page. Under heavy traffic Last.fm will block requests (403/429). Image and page requests use a two-stage fallback:
Proxy (2 attempts) → Direct (1 attempt).
- If
LASTFM_PROXY_URLis set, the request is tried twice through that proxy first. - The direct attempt runs last, rotating User-Agents and sending full browser headers to reduce bot-detection hits.
- If
LASTFM_PROXY_URLis empty, only the direct attempt runs.
LASTFM_PROXY_URL=http://user:pass@host:portMount the /app/data volume to persist:
- SQLite database (
db/database.db) and the generated.app_key - Artist image cache (
cache/artists/) - Generated montages (
montage/) - Application logs (
logs/)
- PHP >= 8.4 with
gd,intl,pdo_sqlite - Composer
cd lastfm/app
composer setup # install, .env, key, migrate, seed admin
composer dev # php artisan serveEdit .env with your settings (LASTFM_API, ADMIN_USER, ADMIN_PASSWORD, ...).
Code style (Pint) and the test suite (Pest):
composer lint
composer test# Process scheduled users (generate montages, mark as QUEUED)
php artisan lastfm:schedule
# Process the queue (send posts to Bluesky/Mastodon)
php artisan lastfm:send
# Force process + send for a single user
php artisan lastfm:force-send {user_id}
# One-time import of the legacy v1 database (automatic in Docker)
php artisan lastfm:import-legacyOutside Docker, run the scheduler yourself:
php artisan schedule:workMade with ❤️ by Butiá Labs