Denali
A simple, fast photoblogging CMS built in Ruby on Rails which features responsive, high-resolution images, a customizable posting schedule, social media management and syndication, a GraphQL API, and more.
Install / Use
/learn @gesteves/DenaliREADME
Denali
A simple, fast photoblogging CMS built in Ruby on Rails which features responsive, high-resolution images. You can see it live at All-Encompassing Trip.
Features
- Simple, streamlined entry editor
- Customizable publishing schedule
- Drag-and-drop organization of queued entries
- Cross-posting of entries to Bluesky, Mastodon, Flickr, Instagram, and Threads
- Auto-tagging of entries by location, camera, lens, film, and style
- Automatic generation of blurhashes for image placeholders
- AI-generated alt text for photos using Claude
- Full-text search powered by Elasticsearch
- Web push notifications
- Webhooks
- Automatic EXIF extraction and geotagging of photos
- Location tagging via Google Maps, the National Park Service, and Native Land APIs
- Admin map view powered by Mapbox GL JS
- GraphQL API
- Automated daily database backups to S3
- Dynamic robots.txt via Known Agents
- RSS/Atom feeds, sitemaps, and Open Graph tags
- Did I mention it's fast as heck?
Requirements
- Docker and Docker Compose (for local development)
- An AWS account with an S3 bucket for image storage and a CloudFront distribution
- A Thumbor image processing service
- A Google Cloud project with OAuth 2.0 credentials for admin authentication
See .env.example for all required and optional environment variables.
Installation & setup
-
Clone the repository:
git clone https://github.com/gesteves/denali.git cd denali -
Copy the example environment file and fill in the required values:
cp .env.example .env -
Build and start the Docker containers:
docker compose up -d --build -
Set up the database:
docker compose run --rm app rails db:setup -
Visit http://localhost:3000.
Common tasks
Local development
The app runs in Docker. All Rails, Ruby, and Node commands must be run inside the container.
Starting the environment
docker compose up -d # Start all services in background
docker compose down # Stop all services
docker compose logs -f app # Follow app logs
Building
docker compose build # Build/rebuild containers
docker compose build --no-cache # Full rebuild (no cache)
docker compose up -d --build # Rebuild and start
Running commands
docker compose run --rm app rails console # Rails console
docker compose run --rm app rails db:migrate # Run migrations
docker compose run --rm app bundle exec rspec # Run tests
docker compose run --rm app rake <task> # Run rake tasks
docker compose run --rm app bundle install # Install gems
docker compose run --rm app bash # Interactive shell
Running tests
# Ruby/Rails (RSpec)
docker compose run --rm app bundle exec rspec
docker compose run --rm app bundle exec rspec spec/models/entry_spec.rb # Specific file
docker compose run --rm app bundle exec rspec spec/models/entry_spec.rb:42 # Specific line
# JavaScript (Vitest)
docker compose run --rm app npm run test:run # Run all tests
docker compose run --rm app npm test # Watch mode
docker compose run --rm app npm run test:coverage # With coverage
Troubleshooting
# Reset the database
docker compose run --rm app rails db:reset
# Clear Rails cache
docker compose run --rm app rails tmp:clear
# Recreate containers from scratch
docker compose down
docker compose up -d --build --force-recreate
# Remove all volumes (deletes local database!)
docker compose down -v
# View container status
docker compose ps
# Check container resource usage
docker compose top
Production
Deploying
fly deploy
Viewing logs
fly logs
SSH into the app
fly ssh console
Rails console
fly ssh console -C "/app/bin/rails console"
Running migrations
Migrations run automatically on deploy, but to run manually:
fly ssh console -C "/app/bin/rails db:migrate"
Running rake tasks
fly ssh console -C "/app/bin/rake <task_name>"
Scaling
# Check current scale
fly scale show
# Scale web & worker machines (count)
fly scale count web=2 worker=2
# Scale machine size
fly scale vm shared-cpu-2x --memory 2048
# Scale worker memory
fly scale memory 2gb -a denali --process-group worker
fly scale memory 1gb -a denali --process-group worker
Backing up the database
The app automatically creates daily database backups and uploads them to S3.
Automated backups:
- Runs daily at 9:00 AM
- Creates PostgreSQL custom format dumps (
.dumpfiles) - Uploads to the S3 bucket specified in
DB_BACKUP_BUCKET - Only runs in production when
DB_BACKUP_BUCKETandDATABASE_URLare set
Create a backup manually:
fly ssh console -C "rake database:backup"
Download the most recent backup:
# Locally (downloads to project root, requires DB_BACKUP_BUCKET and AWS credentials)
docker compose run --rm app rake database:download
Restore a backup:
pg_restore -d database_name denali_backup_YYYYMMDD_HHMMSS.dump
Required environment variables:
DB_BACKUP_BUCKET- S3 bucket name for backupsDATABASE_URL- Postgres connection string (automatically set by Fly.io)AWS_REGION- AWS region (optional, defaults tous-east-1)- AWS credentials (via
AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEYor IAM roles)
