Alexander
Alexander

Reputation: 4287

Rails 4 + Capistrano 3 - Deploy to Production Server from Local Repo

I'm using Windows 10 and Cygwin, and I am setting up Capistrano 3 for deployment to my production environment. I believe I have everything set up correctly, but I can't figure out how to push my local repo to my production server. I don't have my repo on GitHub or another such site and would like to keep it local. I receive the following error message when I run cap production deploy.

SSHKit::Runner::ExecuteError: Exception while executing as [email protected]: git exit status: 128 git stdout: Nothing written git stderr: fatal: No remote configured to list refs from.

SSHKit::Command::Failed: git exit status: 128 git stdout: Nothing written git stderr: fatal: No remote configured to list refs from.

Tasks: TOP => git:check (See full trace by running task with --trace) The deploy has failed with an error: Exception while executing as [email protected]: git exit status : 128 git stdout: Nothing written git stderr: fatal: No remote configured to list refs from.

Here's my config\deploy.rb file:

# config valid only for current version of Capistrano
lock '3.4.0'

set :application, 'myappname
set :deploy_user, 'myappuser'
set :repo_url, "file:///C:/Users/me/Documents/repo/myappname.git"

# setup rvm.
set :rbenv_type, :user
set :rbenv_ruby, '2.1.5-p273'
set :rbenv_prefix, "RBENV_ROOT=#{fetch(:rbenv_path)} RBENV_VERSION=#{fetch(:rbenv_ruby)} #{fetch(:rbenv_path)}/bin/rbenv exec"
set :rbenv_map_bins, %w{rake gem bundle ruby rails}

set :assets_roles, [:app]

# Default value for :scm is :git
set :scm, :git

# Default value for :linked_files is []
set :linked_files, %w{config/database.yml config/application.yml}

# Default value for linked_dirs is []
set :linked_dirs, %w{bin log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system}

# Default value for keep_releases is 5
set :keep_releases, 5

# which config files should be copied by deploy:setup_config
# see documentation in lib/capistrano/tasks/setup_config.cap
# for details of operations
set(:config_files, %w(
  nginx.conf
  application.yml
  database.yml
  unicorn.rb
  unicorn_init.sh
))

# which config files should be made executable after copying
# by deploy:setup_config
set(:executable_config_files, %w(
  unicorn_init.sh
))

# files which need to be symlinked to other parts of the
# filesystem. For example nginx virtualhosts, log rotation
# init scripts etc. The full_app_name variable isn't
# available at this point so we use a custom template {{}}
# tag and then add it at run time.
set(:symlinks, [
  {
    source: "nginx.conf",
    link: "/etc/nginx/sites-enabled/{{full_app_name}}"
  },
  {
    source: "unicorn_init.sh",
    link: "/etc/init.d/unicorn_{{full_app_name}}"
  },
  {
    source: "log_rotation",
    link: "/etc/logrotate.d/{{full_app_name}}"
  }
])

namespace :deploy do
  # compile assets locally then rsync
  after :finishing, 'deploy:cleanup'
end

All of the answers I found didn't have a solution or were for Capistrano 2, so I am wondering if the options have changed. Any help would be greatly appreciated!

EDIT

I've made some progress, but it's still not reading my local repo. Here's the error message:

SSHKit::Command::Failed: git exit status: 128 git stdout: Nothing written git stderr: fatal: '/C:/Users/me/Documents/repo/myappname.git' does not appear to be a git repository fatal: Could not read from remote repository.

Please make sure you have the correct access rights and the repository exists.

I've tried all different path combinations for my repo. My git repo is at the following path: C:/Users/me/Documents/repo/myappname. Is there a specific way to reference my local repo?

Upvotes: 2

Views: 1594

Answers (3)

paascal
paascal

Reputation: 354

Being fed up with Capistrano I decided to go ahead and implement a basic Rake task that does the basic essential things that I need to deploy a small app straight from local. Its a rough draft but it seems to work so far.

# Usage bundle exec rake "deploy:production[/path/to/ssh_key]"
# Assuming you're bundling gems 'net-ssh' + 'net-scp'
namespace :deploy do
  SERVER = '<server_url_or_ip'
  USER = '<user>'
  DEPLOY_TO = '/deploy/to/folder'
  SHARED_PATH = "#{DEPLOY_TO}/shared"
  CURRENT_PATH = "#{DEPLOY_TO}/current"
  RBENV_PATH = '<path_to_rbenv>'

  # ANSI color codes
  GREEN = "\e[32m"
  YELLOW = "\e[33m"
  BLUE = "\e[34m"
  RESET = "\e[0m"

  def log(message, color = BLUE)
    puts "#{color}#{message}#{RESET}"
  end

  def log_task(task_name)
    puts "\n#{YELLOW}#{'-' * 50}#{RESET}"
    log "Starting: #{task_name}", YELLOW
  end

  desc "Deploy the application to production (use quotes around argument in Zsh: \"deploy:production[/path/to/key]\")"
  task :production, [:ssh_key] => [:confirm] do |t, args|
    ssh_key = args[:ssh_key] || raise("Please provide the SSH key path: rake \"deploy:production[/path/to/ssh_key]\"")
    commit_hash = `git rev-parse --short HEAD`.strip
    release_name = "release-#{Time.now.strftime('%Y%m%d%H%M%S')}-revision-#{commit_hash}"

    log "Starting deployment of #{release_name}", GREEN
    Rake::Task["deploy:package"].invoke(release_name)
    Rake::Task["deploy:upload"].invoke(ssh_key, release_name)
    Rake::Task["deploy:extract"].invoke(ssh_key, release_name)
    Rake::Task["deploy:create_dirs"].invoke(ssh_key)
    Rake::Task["deploy:symlink"].invoke(ssh_key, release_name)

    ruby_version = get_ruby_version(ssh_key, release_name)

    Rake::Task["deploy:bundle_install"].invoke(ssh_key, release_name, ruby_version)
    Rake::Task["deploy:db_migrate"].invoke(ssh_key, ruby_version)
    Rake::Task["deploy:assets_precompile"].invoke(ssh_key, ruby_version)
    Rake::Task["deploy:restart"].invoke(ssh_key)
    Rake::Task["deploy:cleanup_remote"].invoke(ssh_key)
    Rake::Task["deploy:cleanup_local"].invoke(release_name)
    log "Deployment of #{release_name} completed successfully", GREEN
  end

  task :confirm do
    print "Are you sure you want to deploy to production? (y/n): "
    exit unless STDIN.gets.chomp.downcase == 'y'
  end

  task :package, [:release_name] do |t, args|
    log_task "Packaging application"
    sh "git archive --format=tar --output=./#{args[:release_name]}.tar HEAD"
  end

  task :upload, [:ssh_key, :release_name] do |t, args|
    log_task "Uploading package to server"
    Net::SCP.start(SERVER, USER, keys: [args[:ssh_key]]) do |scp|
      scp.upload!("./#{args[:release_name]}.tar", "#{DEPLOY_TO}/releases/#{args[:release_name]}.tar")
    end
  end

  task :extract, [:ssh_key, :release_name] do |t, args|
    log_task "Extracting package on server"
    release_path = "#{DEPLOY_TO}/releases/#{args[:release_name]}"
    ssh_exec("mkdir -p #{release_path} && tar -xf #{DEPLOY_TO}/releases/#{args[:release_name]}.tar -C #{release_path}", args[:ssh_key])
  end

  task :create_dirs, [:ssh_key] do |t, args|
    log_task "Creating necessary directories"
    ssh_exec("mkdir -p #{SHARED_PATH}/tmp/{sockets,pids,log}", args[:ssh_key])
  end

  task :symlink, [:ssh_key, :release_name] do |t, args|
    log_task "Symlinking current to new release"
    release_path = "#{DEPLOY_TO}/releases/#{args[:release_name]}"
    ssh_exec("ln -sfn #{release_path} #{CURRENT_PATH}", args[:ssh_key])
  end

  task :bundle_install, [:ssh_key, :release_name, :ruby_version] do |t, args|
    log_task "Installing gems"
    release_path = "#{DEPLOY_TO}/releases/#{args[:release_name]}"
    ssh_exec("cd #{release_path} && #{RBENV_PATH} local #{args[:ruby_version]} && #{RBENV_PATH} exec bundle install --deployment --without development test", args[:ssh_key])
  end

  task :db_migrate, [:ssh_key, :ruby_version] do |t, args|
    log_task "Running database migrations"
    ssh_exec("cd #{CURRENT_PATH} && #{RBENV_PATH} local #{args[:ruby_version]} && #{RBENV_PATH} exec bundle exec rake db:migrate RAILS_ENV=production", args[:ssh_key])
  end

  task :assets_precompile, [:ssh_key, :ruby_version] do |t, args|
    log_task "Precompiling assets"
    ssh_exec("cd #{CURRENT_PATH} && #{RBENV_PATH} local #{args[:ruby_version]} && #{RBENV_PATH} exec bundle exec rake assets:precompile RAILS_ENV=production", args[:ssh_key])
  end

  task :restart, [:ssh_key] do |t, args|
    log_task "Restarting services"
    %w(paint sidekiq nginx puma).each do |service|
      ssh_exec("sudo systemctl restart #{service}", args[:ssh_key])
    end
  end

  task :cleanup_remote, [:ssh_key] do |t, args|
    log_task "Cleaning up old releases on server"
    ssh_exec("cd #{DEPLOY_TO}/releases && ls -1tr | head -n -2 | xargs -d '\\n' rm -rf --", args[:ssh_key])
  end

  task :cleanup_local, [:release_name] do |t, args|
    log_task "Cleaning up local package"
    sh "rm -f ./#{args[:release_name]}.tar"
  end

  def get_ruby_version(ssh_key, release_name)
    release_path = "#{DEPLOY_TO}/releases/#{release_name}"
    ssh_exec("cat #{release_path}/.ruby-version", ssh_key).strip
  end

  def ssh_exec(command, ssh_key)
    Net::SSH.start(SERVER, USER, keys: [ssh_key]) do |ssh|
      puts "Executing: #{command}"
      output = ssh.exec!(command)
      puts output if output
      exit_code = ssh.exec!("echo $?").to_i
      raise "Command failed: #{command}" if exit_code != 0
      output.strip
    end
  rescue => e
    puts "Error: #{e.message}"
    exit 1
  end
end

Upvotes: 0

Dmitry Polyakovsky
Dmitry Polyakovsky

Reputation: 1585

I have been using capistrano-scm-copy to do my deploys via SCP upload from my dev box to AWS. Note of caution - this is like running with scissors. It's nice to be able to do a quick hotfix deploy, verily it in production and the do git commit. But you need to be really careful to not forget the commit part.

Upvotes: 1

Matt Brictson
Matt Brictson

Reputation: 11102

Unfortunately, this is not how Capistrano is designed to work. The basic assumption Capistrano makes is that your project resides in a repository that the server can access (almost always this is a Git repo hosted at e.g. Bitbucket or GitHub).

Capistrano pulls your source code by running git commands on the server. The server has no way to see your local repository, which is why it doesn't work.

There are some plugins that try to change Capistrano's behavior to allow for the push style deployment you are looking for. Search GitHub for "capistrano copy" and try them out. This one could be what you're looking for:

https://github.com/ydkn/capistrano-git-copy

Upvotes: 2

Related Questions