Capistrano and Net:SSH with login shell

New to Capistrano? Read my previous and more introductory blog post on Capistrano – Remote builds with Capistrano.

Are you using Capistrano and are confused why the user environment, the PATH for example, is different from when you log on to your server via SSH? Perhaps you have failed to use RVM or other user specific applications when running your Capistrano scripts? This post will explain why this is, and what can be done to solve the problem.

By default Capistrano will execute remote commands by something like so:

Net::SSH.start( host, user, password: pass ) do |ssh|
  ssh.open_channel do |channel|
    channel.exec("sh -c '#{command}'")

There is no interactive shell and only the system’s environment is loaded.

With Net:SSH there are at least two ways to execute commands in a login shell. The first is to send the commands after requesting a shell.

ssh.open_channel do |channel|
  channel.send_channel_request "shell" do |ch, success|
    if success
      ch.send_data "#{command}n"
      ch.send_data "exitn"
      raise "could not start user shell"

The second way is to make your shell act as if it had been invoked as a login shell.

channel = ssh.open_channel do |channel|
  channel.exec("bash -l") do |ch, success|
    ch.send_data "#{command}n"
    ch.send_data "exitn"

Now we know that Capistrano does not give us a user shell. So, what can we do about it? Capistrano actually has an option for changing the default shell. Instead of all remote commands being executed as sh -c '<command>' you can have them executed as <your shell> -c '<command>'. The shell will still not be invoked as a login shell though, and thus no files that sets your user’s environment will be loaded. The trick here is to set the default shell to bash -l (or other shell that can be started in interactive mode).

set :default_shell, "bash -l"

With this line added to your capfile or task, the environment should be exactly the same as when you log on to your server via SSH. For instance, if you are using RVM, the task

task :ruby_path, :hosts => "" do
  set :default_shell, "bash -l"
  run "which ruby"

should show that your default RVM ruby is used, rather than the system-global ruby-installation:

** [out ::] /home/johan/.rvm/rubies/ruby-1.9.2-p290/bin/ruby

Hopefully, Capistrano will have proper support for login shell someday soon. Until then, enjoy this little hack.

This Post Has 8 Comments

  1. Andy Adams

    Thank you so much for this – you saved me from burning even more time than I already had :D!

  2. Thien-An Mac

    Just a quick correction:

    ch.send_data “#{command}n”

    should be ch.send_data “#{command}n”

    took me a while to figure out why my commands are being sent!

  3. Johan Lundahl

    @Thien-An, thank you! Have corrected this now.
    I am quite sure this was correct before. It must have changed on some wordpress upgrade or something.

  4. Michael Smith

    Thank you, I’ve been messing around with NetSSH for a while trying to trigger login shell and the first method that you mentioned was the key.

    Great article!

  5. Matteo Giachino

    Thank you!

  6. swapnil

    Hey how can we use the same logic in capistrano 3.5.
    i.e. i want capistano to use to use bash shell instead of sh shell.

    Above suggestion does not work in 3.5.0 :(

Leave a Reply