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 directoryfor config files shared/config/database.ymlsymlink points to old release path- Application starts but crashes with missing credentials error
shared/public/uploadsdirectory 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_filesreferenced but not uploaded to serverlinked_dirsparent 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.Verify deploy.rb linked_files configuration:
- 2.```ruby
- 3.# config/deploy.rb
- 4.# Files that must exist in shared/ and be symlinked into each release
- 5.append :linked_files, "config/database.yml", "config/master.key", "config/credentials/production.key"
- 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.Create shared directory structure on server:
- 2.```bash
- 3.# SSH to server
- 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.Upload missing linked files:
- 2.```bash
- 3.# From local machine, upload config files
- 4.scp config/database.yml deploy@production.example.com:/var/www/app/shared/config/
- 5.scp config/credentials/production.key deploy@production.example.com:/var/www/app/shared/config/credentials/
- 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.Fix broken symlinks in current release:
- 2.```bash
- 3.# SSH to server
- 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.Add Capistrano task to verify symlinks before deploy:
- 2.```ruby
- 3.# lib/capistrano/tasks/symlink_check.rake
- 4.namespace :deploy do
- 5.desc "Verify all linked_files and linked_dirs exist in shared/"
- 6.task :check_symlinks do
- 7.on roles(:app) do
- 8.# Check linked files
- 9.fetch(:linked_files).each do |file|
- 10.shared_path = "#{fetch(:shared_path)}/#{file}"
- 11.unless test("[ -f #{shared_path} ]")
- 12.warn "MISSING: #{shared_path}"
- 13.warn "Run: scp #{file} #{host}@#{shared_path}"
- 14.exit 1
- 15.end
- 16.info "OK: #{shared_path}"
- 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_symlinksas a pre-deploy step in CI/CD - Use
cap production deploy:checkbefore 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:uploadfor one-off file uploads