Rails Uygulamasını Linux Sunucuya Deploy Etme
Production’da Rails uygulaması deploy etmek, geliştirici hayatının en stresli anlarından biri olabilir. Özellikle ilk kez yapıyorsan, “neden çalışmıyor?” sorusuyla saatlerce boğuşmak kaçınılmaz oluyor. Bu yazıda sıfırdan başlayıp, gerçek bir Rails uygulamasını Ubuntu 22.04 sunucusuna nasıl deploy edeceğini adım adım anlatacağım. Nginx, Puma, Systemd ve Capistrano gibi araçları birlikte kullanarak production’a hazır bir kurulum yapacağız.
Sunucu Hazırlığı ve Temel Gereksinimler
İlk iş sunucuya bağlanıp sistemi güncel hale getirmek. Bunu atlamak sonradan başını ağrıtabilir.
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl wget git build-essential libssl-dev libreadline-dev
zlib1g-dev libsqlite3-dev sqlite3 libpq-dev postgresql postgresql-contrib
nginx nodejs npm
Şimdi deploy işlemleri için ayrı bir kullanıcı oluşturalım. Root ile deploy yapmak güvenlik açığı demektir, bunu asla tavsiye etmem.
sudo adduser deploy
sudo usermod -aG sudo deploy
su - deploy
Bu noktada SSH key authentication kurmanı da şiddetle tavsiye ederim. Parola ile giriş production sunucularında açık davetiyedir.
# Lokal makinende
ssh-keygen -t ed25519 -C "deploy@myapp"
ssh-copy-id deploy@sunucu_ip_adresi
Ruby Kurulumu: rbenv ile Doğru Yol
Ruby kurulumu için rbenv kullanacağız. rvm de bir seçenek ama rbenv daha hafif ve yönetimi daha kolay. Sistemin Ruby’sini kullanmak ise versiyonlar arası uyumsuzluk cehennemi yaratır, yaşanmış tecrübeden söylüyorum.
# deploy kullanıcısı olarak çalıştır
git clone https://github.com/rbenv/rbenv.git ~/.rbenv
cd ~/.rbenv && src/configure && make -C src
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
source ~/.bashrc
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
Ruby kurulumu biraz zaman alır, sabırlı ol:
rbenv install 3.2.2
rbenv global 3.2.2
ruby -v # ruby 3.2.2 görmelisin
gem install bundler --no-document
rbenv rehash
--no-document flag’i kurulum süresini ciddi oranda kısaltır. Production sunucusunda gem dokümantasyonuna ihtiyacın yok zaten.
PostgreSQL Kurulumu ve Yapılandırması
Gerçek dünya uygulamalarında SQLite kullanmak pek mümkün değil. PostgreSQL kurup Rails uygulaması için bir veritabanı ve kullanıcı oluşturalım.
sudo systemctl start postgresql
sudo systemctl enable postgresql
sudo -u postgres psql
PostgreSQL shell’inde şunları çalıştır:
CREATE USER myapp_user WITH PASSWORD 'guclu_bir_sifre_yaz';
CREATE DATABASE myapp_production OWNER myapp_user;
GRANT ALL PRIVILEGES ON DATABASE myapp_production TO myapp_user;
q
Veritabanı şifresini saklamak için environment variable kullanacağız. Şifreyi doğrudan kod içine ya da database.yml‘a yazmak büyük bir hata. Bu konuya birazdan geleceğiz.
Uygulama Dizin Yapısını Oluşturma
Capistrano ile deploy yapacağımız için klasik dizin yapısını manuel olarak da anlaman gerekiyor. Bu yapıyı kavramak sorun gidermeyi çok kolaylaştırır.
sudo mkdir -p /var/www/myapp
sudo chown deploy:deploy /var/www/myapp
# deploy kullanıcısı olarak
mkdir -p /var/www/myapp/{releases,shared,shared/config,shared/log,shared/tmp/pids,shared/tmp/sockets,shared/public/system}
Dizin yapısı şu şekilde olacak:
- /var/www/myapp/releases: Her deploy bir klasör olarak burada durur
- /var/www/myapp/current: Son deploy’a symbolic link
- /var/www/myapp/shared: Deploylar arası paylaşılan dosyalar (log, config, upload’lar)
Environment Variables ve Gizli Bilgiler
Production’da gizli bilgileri yönetmek kritik önem taşır. .env dosyası ya da doğrudan systemd service dosyasında environment variable tanımlamak en pratik yollardan biri.
# /var/www/myapp/shared/config/.env dosyası oluştur
nano /var/www/myapp/shared/config/.env
Dosya içeriği:
DATABASE_URL=postgresql://myapp_user:guclu_bir_sifre_yaz@localhost/myapp_production
SECRET_KEY_BASE=rails_secret_key_base_buraya_gelecek
RAILS_ENV=production
RAILS_SERVE_STATIC_FILES=true
RAILS_LOG_TO_STDOUT=true
SECRET_KEY_BASE üretmek için Rails’in kendi aracını kullan:
# Geliştirme ortamında çalıştır
cd myapp_dizini
bundle exec rails secret
Bu dosyanın izinlerini kısıtlamayı unutma:
chmod 600 /var/www/myapp/shared/config/.env
Capistrano Kurulumu ve Yapılandırması
Capistrano, Rails deploy işlemini otomatize etmenin standart yolu. Gemfile’a gerekli gem’leri ekle:
group :development do
gem 'capistrano', '~> 3.17'
gem 'capistrano-rails', '~> 1.6'
gem 'capistrano-rbenv', '~> 2.2'
gem 'capistrano-bundler', '~> 2.0'
gem 'capistrano3-puma', '~> 5.2'
end
bundle install
bundle exec cap install
Bu komut Capfile, config/deploy.rb ve config/deploy/production.rb dosyalarını oluşturur. Capfile içeriği şöyle olmalı:
require "capistrano/setup"
require "capistrano/deploy"
require "capistrano/rbenv"
require "capistrano/bundler"
require "capistrano/rails/assets"
require "capistrano/rails/migrations"
require "capistrano/puma"
install_plugin Capistrano::Puma
install_plugin Capistrano::Puma::Systemd
Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }
config/deploy.rb dosyasını düzenle:
lock "~> 3.17.0"
set :application, "myapp"
set :repo_url, "[email protected]:kullanici/myapp.git"
set :deploy_to, "/var/www/myapp"
set :rbenv_type, :user
set :rbenv_ruby, "3.2.2"
set :linked_files, %w[config/.env]
set :linked_dirs, %w[log tmp/pids tmp/cache tmp/sockets vendor/bundle
public/system public/uploads]
set :keep_releases, 5
set :puma_threads, [4, 16]
set :puma_workers, 2
set :puma_bind, "unix://#{shared_path}/tmp/sockets/puma.sock"
set :puma_state, "#{shared_path}/tmp/pids/puma.state"
set :puma_pid, "#{shared_path}/tmp/pids/puma.pid"
append :linked_dirs, ".bundle"
config/deploy/production.rb dosyası:
server "sunucu_ip_adresi", user: "deploy", roles: %w[app db web]
set :ssh_options, {
keys: %w[~/.ssh/id_ed25519],
forward_agent: true,
auth_methods: %w[publickey]
}
Nginx Yapılandırması
Puma uygulama sunucusu olarak çalışacak, Nginx ise önünde reverse proxy görevi görecek. SSL termination, static file serving ve load balancing için Nginx’i kullanmak doğru yaklaşım.
sudo nano /etc/nginx/sites-available/myapp
upstream puma {
server unix:///var/www/myapp/shared/tmp/sockets/puma.sock;
}
server {
listen 80;
server_name myapp.com www.myapp.com;
root /var/www/myapp/current/public;
access_log /var/log/nginx/myapp_access.log;
error_log /var/log/nginx/myapp_error.log info;
location ^~ /assets/ {
gzip_static on;
expires max;
add_header Cache-Control public;
}
location / {
try_files $uri @puma;
}
location @puma {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://puma;
}
error_page 500 502 503 504 /500.html;
client_max_body_size 10M;
keepalive_timeout 10;
}
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t # Konfigürasyonu test et
sudo systemctl reload nginx
Puma Systemd Service
Puma’yı systemd servisi olarak çalıştırmak, sunucu yeniden başladığında uygulamanın otomatik ayağa kalkmasını sağlar. Capistrano’nun puma plugin’i bunu otomatik yapar ama manuel kurulum için şu adımları izle:
sudo nano /etc/systemd/system/puma_myapp.service
[Unit]
Description=Puma HTTP Server for myapp
After=network.target
[Service]
Type=simple
User=deploy
WorkingDirectory=/var/www/myapp/current
EnvironmentFile=/var/www/myapp/shared/config/.env
ExecStart=/home/deploy/.rbenv/bin/rbenv exec bundle exec puma -C /var/www/myapp/shared/puma.rb
ExecReload=/bin/kill -TSTP $MAINPID
StandardOutput=append:/var/www/myapp/shared/log/puma_access.log
StandardError=append:/var/www/myapp/shared/log/puma_error.log
Restart=always
RestartSec=1
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable puma_myapp
sudo systemctl start puma_myapp
sudo systemctl status puma_myapp
İlk Deploy ve Sık Karşılaşılan Sorunlar
Her şey hazır, ilk deploy’u yapalım:
# Lokal makinende
bundle exec cap production deploy
Capistrano önce bağlantıyı test eder, repository’yi klonlar, bundle install yapar, asset’leri compile eder, migration’ları çalıştırır ve Puma’yı yeniden başlatır. İlk seferde birkaç sorunla karşılaşman normal.
Sık karşılaşılan hatalar:
- Permission denied (publickey): SSH agent forwarding aktif değil.
ssh-add ~/.ssh/id_ed25519komutunu çalıştır. - Bundler::GemNotFound: Server’da bundle install başarısız olmuş.
shared/log/capistrano.logdosyasına bak. - PG::ConnectionBad: Veritabanı bağlantısı kurulamıyor. DATABASE_URL’i kontrol et.
- Assets precompile hatası: Node.js sürüm uyumsuzluğu olabilir.
nvmile doğru versiyonu yükle.
Deploy sonrası uygulamanın durumunu kontrol et:
# Sunucuda
sudo systemctl status puma_myapp
tail -f /var/www/myapp/shared/log/puma_error.log
sudo tail -f /var/log/nginx/myapp_error.log
Let’s Encrypt ile SSL Kurulumu
HTTP üzerinden production uygulaması çalıştırmak 2024’te kabul edilemez. Certbot ile ücretsiz SSL sertifikası alalım:
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d myapp.com -d www.myapp.com
Certbot, Nginx konfigürasyonunu otomatik olarak güncelleyecek. Sertifika yenileme için cron job zaten otomatik kurulur, ama test etmek istersin:
sudo certbot renew --dry-run
Zero-Downtime Deploy için İnce Ayarlar
Production’da uygulamayı deploy ederken kullanıcıların kesinti yaşamaması için Puma’nın phased restart özelliğini kullan. puma.rb konfigürasyonuna şunları ekle:
# /var/www/myapp/shared/puma.rb
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
threads threads_count, threads_count
port ENV.fetch("PORT") { 3000 }
environment ENV.fetch("RAILS_ENV") { "production" }
pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }
workers ENV.fetch("WEB_CONCURRENCY") { 2 }
preload_app!
on_worker_boot do
ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
end
before_fork do
ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord)
end
plugin :tmp_restart
preload_app! ile birlikte on_worker_boot ve before_fork hook’larını kullanmak, veritabanı bağlantı sorunlarını önler. Bu detayı atlayan birçok tutorial gördüm, production’da ciddi sorunlara yol açıyor.
Log Yönetimi ve Monitoring
Uygulama loglarını düzenli olarak kontrol etmek ve rotate etmek önemli. Logrotate konfigürasyonu ekle:
sudo nano /etc/logrotate.d/myapp
/var/www/myapp/shared/log/*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
copytruncate
sharedscripts
postrotate
systemctl reload puma_myapp > /dev/null 2>&1 || true
endscript
}
Basit bir uptime monitoring için monit kurabilirsin:
sudo apt install monit -y
sudo nano /etc/monit/conf.d/puma
check process puma
with pidfile /var/www/myapp/shared/tmp/pids/puma.pid
start program = "/bin/systemctl start puma_myapp"
stop program = "/bin/systemctl stop puma_myapp"
if failed unixsocket /var/www/myapp/shared/tmp/sockets/puma.sock then restart
if 5 restarts within 5 cycles then timeout
Güvenlik Duvarı Ayarları
UFW ile sadece gerekli portları açık bırak:
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw status
Nginx Full hem 80 hem de 443 portunu açar. SSH portunu değiştirdiysen buna dikkat et, aksi halde sunucuya erişimi kaybedebilirsin.
Deployment Sonrası Rutin Kontroller
Her deploy’dan sonra şu komutları çalıştırma alışkanlığı edinmek gerekir:
# Uygulama sağlıklı mı?
curl -I https://myapp.com
# Puma worker'ları çalışıyor mu?
sudo systemctl status puma_myapp
# Veritabanı bağlantısı var mı?
cd /var/www/myapp/current
bundle exec rails runner "puts ActiveRecord::Base.connection.active?"
# Bellek kullanımı
ps aux | grep puma | awk '{print $1, $2, $4, $11}'
# Son deploy ne zaman yapıldı?
ls -lt /var/www/myapp/releases | head -5
Rollback gerekirse Capistrano’nun rollback komutu hayat kurtarır:
bundle exec cap production deploy:rollback
Bu komut önceki release’e geri döner ve Puma’yı yeniden başlatır. Birkaç saniye içinde eski versiyona dönmüş olursun.
Sonuç
Rails uygulaması deploy etmek ilk bakışta karmaşık görünse de doğru araçları kullandığında oldukça yönetilebilir bir süreç. Bu yazıda anlattıklarımı özetlersek: rbenv ile Ruby kurulumu, PostgreSQL ile veritabanı, Puma ile uygulama sunucusu, Nginx ile reverse proxy, Capistrano ile otomatik deploy ve Systemd ile process yönetimi. Bu stack production’da yıllardır kanıtlanmış bir kombinasyon.
Gözden kaçırılan en büyük detaylar genellikle güvenlik tarafında oluyor: environment variable’ları yanlış yönetmek, root ile deploy yapmak, SSL kurmamak ya da güvenlik duvarını yapılandırmamak. Bu yazıdaki adımları takip edersen bunların hepsini doğru şekilde halletmiş olursun.
İlk deploy başarısız olursa paniğe kapılma. Log dosyalarına bak, hatayı Google’la ve Capistrano’nun verbose modunu kullan: bundle exec cap production deploy --trace. Sorunun kaynağını bulmak her zaman mümkün, sadece sabır gerekiyor.
