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_ed25519 komutunu çalıştır.
  • Bundler::GemNotFound: Server’da bundle install başarısız olmuş. shared/log/capistrano.log dosyası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. nvm ile 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.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir