Introduction

Capistrano deploys to a timestamped release directory and creates symlinks from shared/ into each release. When shared directory symlinks break or point to non-existent targets, the application cannot find critical files like database configuration, credentials, uploaded files, or SSL certificates. This causes deployment to fail silently or the application starts with missing configuration.

Symptoms

  • Deployment fails with No such file or directory for config files
  • shared/config/database.yml symlink points to old release path
  • Application starts but crashes with missing credentials error
  • shared/public/uploads directory not accessible after deploy
  • Capistrano output shows ln: failed to create symbolic link

Error during deploy: `` 00:00 deploy:symlink:linked_files 01 ln -s /var/www/app/shared/config/database.yml /var/www/app/releases/20260409100000/config/database.yml 01 ln: failed to create symbolic link '/var/www/app/releases/20260409100000/config/database.yml': No such file or directory

Common Causes

  • Shared directory not created on first deploy
  • linked_files referenced but not uploaded to server
  • linked_dirs parent directory missing on server
  • Permissions prevent symlink creation (wrong deploy user)
  • Disk full preventing symlink target creation
  • Manual file edits on server not in shared directory

Step-by-Step Fix

  1. 1.Verify deploy.rb linked_files configuration:
  2. 2.```ruby
  3. 3.# config/deploy.rb
  4. 4.# Files that must exist in shared/ and be symlinked into each release
  5. 5.append :linked_files, "config/database.yml", "config/master.key", "config/credentials/production.key"
  6. 6.append :linked_files, ".env", "config/sidekiq.yml"

# Directories that persist across deploys append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "public/uploads", "public/assets" ```

  1. 1.Create shared directory structure on server:
  2. 2.```bash
  3. 3.# SSH to server
  4. 4.ssh deploy@production.example.com

# Create shared directories cd /var/www/app mkdir -p shared/config shared/log shared/tmp/pids shared/tmp/sockets shared/tmp/cache shared/public/uploads

# Set correct permissions chown -R deploy:deploy shared/ chmod 750 shared/ chmod 640 shared/config/* ```

  1. 1.Upload missing linked files:
  2. 2.```bash
  3. 3.# From local machine, upload config files
  4. 4.scp config/database.yml deploy@production.example.com:/var/www/app/shared/config/
  5. 5.scp config/credentials/production.key deploy@production.example.com:/var/www/app/shared/config/credentials/
  6. 6.scp .env deploy@production.example.com:/var/www/app/shared/

# Verify files exist ssh deploy@production.example.com "ls -la /var/www/app/shared/config/" ```

  1. 1.Fix broken symlinks in current release:
  2. 2.```bash
  3. 3.# SSH to server
  4. 4.ssh deploy@production.example.com

# Check current release cd /var/www/app/current ls -la config/database.yml # Verify symlink target

# If broken, recreate manually cd /var/www/app/releases/LATEST_RELEASE rm -f config/database.yml ln -s ../../shared/config/database.yml config/database.yml ln -s ../../shared/config/master.key config/master.key ln -s ../../shared/public/uploads public/uploads ```

  1. 1.Add Capistrano task to verify symlinks before deploy:
  2. 2.```ruby
  3. 3.# lib/capistrano/tasks/symlink_check.rake
  4. 4.namespace :deploy do
  5. 5.desc "Verify all linked_files and linked_dirs exist in shared/"
  6. 6.task :check_symlinks do
  7. 7.on roles(:app) do
  8. 8.# Check linked files
  9. 9.fetch(:linked_files).each do |file|
  10. 10.shared_path = "#{fetch(:shared_path)}/#{file}"
  11. 11.unless test("[ -f #{shared_path} ]")
  12. 12.warn "MISSING: #{shared_path}"
  13. 13.warn "Run: scp #{file} #{host}@#{shared_path}"
  14. 14.exit 1
  15. 15.end
  16. 16.info "OK: #{shared_path}"
  17. 17.end

# Check linked dirs fetch(:linked_dirs).each do |dir| shared_path = "#{fetch(:shared_path)}/#{dir}" unless test("[ -d #{shared_path} ]") warn "MISSING: #{shared_path}" warn "Run: mkdir -p #{shared_path}" execute :mkdir, "-p", shared_path end info "OK: #{shared_path}" end end end end

# Hook into deploy flow before "deploy:starting", "deploy:check_symlinks" ```

Prevention

  • Add cap deploy:check_symlinks as a pre-deploy step in CI/CD
  • Use cap production deploy:check before every deploy
  • Store shared directory setup in infrastructure-as-code (Ansible, Terraform)
  • Never edit files directly in release directories (they disappear on next deploy)
  • Document all required shared files in deployment runbook
  • Use cap production deploy:upload for one-off file uploads