Use Ruby Deploy Hooks

Deploy hooks are Ruby scripts that you write which are executed at designated points in the deployment process. This allows you to customize the deployment of your application to meet its particular needs.

For example, if your application uses Resque, then deploy hooks provide a way for you to restart your Resque workers when you deploy a new version of your application. Rollbar users can use deploy hooks to notify Rollbar of a deploy.

Deploy hooks live in the APP_ROOT/deploy directory of your application. The order in which they run is specified in the documentation for the ey deploy command.

Important: When you add a new instance, Engine Yard automatically runs the deploy hooks. input_ref will not be available and the deploy hooks will fail if you use it. This is a known issue and we are working on a fix. In the meantime, confirm that input_ref is not nil and repo is nil.

Best Practices

Engine Yard recommends that you use deploy hooks to run rake tasks or scripts that perform the necessary tasks, rather than including all the logic in the hook itself. You should use deploy hooks mostly to trigger shell commands whenever possible. Using deploy hooks in this way allows you to write tests for your rake tasks and scripts, then the hook simply triggers these tasks and scripts.

You should avoid using deploy hooks as part of your application code because they are instance_eval'd into a running copy of the deploy system. This means if you you try to use hooks as part of your code, you will be battling with all the context in the deploy code.

Structure and sequence

To use deploy hooks, create an APP_ROOT/deploy directory in your application and save named hook files in this directory which will be triggered at the appropriate times during the deployment process. The files are defined as follows and run in the order listed:

APP_ROOT/ 
deploy/
before_bundle.rb
after_bundle.rb
before_compile_assets.rb
after_compile_assets.rb
before_migrate.rb
after_migrate.rb
before_symlink.rb
after_symlink.rb
before_restart.rb
after_restart.rb

Remember that, in order for migrations to run, your entire environment is loaded. So if you have any symlinks that need to be created in order for the application to start properly, put them in before_migrate.rb instead of before_symlink.rb, because before_symlink.rb runs after the migration.

Any deploy hooks that you have defined are called, even if they are hooking into a step that is not necessary for the deployment. For example, after_migrate is called even if there are no new migrations in your deployment.

Shell commands

Besides the usual ruby syntax, you also have access to run, run!, sudo, and sudo! to run commands. See Deploy Hooks API.

Command Runs as    Exit zero    Non-zero exit    
run deploy user returns true returns false
run! deploy user returns true aborts deploy
sudo root returns true returns false
sudo! root returns true aborts deploy

For example:

run "echo ‘config.release_path: #{config.release_path}’ >> #{config.shared_path}/logs.log"  
run "ln -nfs #{config.shared_path}/config/foo.yml #{config.release_path}/config/foo.yml"
sudo "echo ‘sudo works’ >> /root/sudo.log"

Calling git commands

Here’s an example where you can call a git command from a deploy hook:

run "exec ssh-agent bash -c 'ssh-add /home/deploy/.ssh/<app>-deploy-key && git clone git@github.com:foo/bar.git #{current_path}/tmp/foo'"

Replace <app> with the name of your app and change the path for your git repository.

Logging

Deploy hooks can output to STDERR, which will be written to the deploy log and you can use the warning command do this.

For example, this line on a deploy hook:

warning "Hello there, STDERR!"

will produce this output in the log:

     +    00s  !> WARNING: Hello there, STDERR!

Another option which you might prefer is to run an echo command that outputs to STDERR:

run "echo 'Hello from run, STDERR!' 1>&2"

The above code in a deploy hook will output:

     +    00s       Hello from run, STDERR!

which is a litter more appropriate because it does not include the WARNING string.

 

Deploy hooks API

Engine Yard has deprecated the use of the @configuration variable directly. Instead,use the config object for compatibility with future releases.

The following methods are Ruby specific and should be accessed through the config object: For non-Ruby specific deploy hook variables, see Use non-Ruby Deploy Hooks

  • config.account_name

    The name of the account that owns the environment where the application is deploying: e.g. myaccount

  • config.all_releases

    Array of paths to the deployments on this instance, ordered from oldest to newest.

  • config.app

    This is the name of the application: e.g. myapp

  • config.current_name

    On a utility instance, the name of the utility instance. On non-utility instances, nil.

  • config.current_path

    Path to the current deployment. This is the deployment pointed to by the current symlink, so the value of current_path() will change after the symlink step is executed.

  • config.current_role

    The role of the current instance. Possible values:

    • solo: the sole instance for the environment
    • app_master: the application master
    • app: a non-master application server
    • util: a utility server. The current_name method will allow you to distinguish between different types of utility servers.
  • config.deployed_by

    The name of the user who triggered the deployment: e.g. John Smith.  Not available on rollback.

  • config.framework_env

    The value of the RAILS_ENV, RACK_ENV, and MERB_ENV environment variable.

  • config.environment_name

    This is the name of the environment that the application is deploying on: e.g. myenvironment or myapp_staging.

  • config.input_ref

    The name of the branch that was given as input to the deploy: e.g. master or branch.  Not available on rollback.

    Note: Roll back is ideal for simple deployments with no database migrations or complex deploy hooks. When a data transformation has been deployed, rolling forward by deploying a corrected release may be best.

  • config.latest_release

    Path to the most recent deployment.

  • config.migrate?

    True if migrations are being run in this deployment, false otherwise.

  • config.node

    Various information about the current instance and other instances in the environment. See /etc/chef/dna.json on an instance for an example of what node will return. If the information you require is available via some other method, it is preferable to use the other method to get it. node is just here as a catch-all.

    Note: There were some changes in the dna.json structure on V5. Most of the keys have been moved under the 'dna' key. For example, on V4 you refer to the environment as config.node[:environment][:name]. For V5 you should change that to config.node[:dna][:environment][:name]. In this case, it is better to use config.environment_name instead, because that works for both V4 and V5.

  • config.previous_release(current=latest_release)

    Path to the deployment prior to current. If nil, then current is the newest deployment.

  • config.oldest_release

    Path to the oldest deployment

  • config.release_dir

    Path to the directory containing the deployments

  • config.release_path

    Path to the deployment that is currently being deployed: e.g. /data/appname/releases/12345678

  • config.repo

    The URL of this application’s git repository.

  • config.repository_cache

    The path to the local clone of this application’s git repository.

  • config.active_revision

    The SHA of the commit currently being deployed.

  • config.shared_path

    Path to the shared dir: e.g. /data/appname/shared

  • config.stack

    The web server stack for this environment. Possible values:

    • nginx_mongrel: Nginx web server and Mongrel application server
    • nginx_passenger: Nginx web server and Passenger application server
    • nginx_unicorn: Nginx web server and Unicorn application server

Helper Methods

The following are helper methods in the deploy hook:

  • debug()

    Logs messages to STDOUT and allows you to add log-in capabilities to your deploy hooks. Only shows when verbose is true.

  • info()

    Logs messages to STDOUT and allows you to add log-in capabilities to your deploy hooks.

  • on_app_master(&block)

    Executes block only on the environment’s application master or single instance.

  • on_app_servers(&block)

    Executes block only on the environment’s application servers (master and slaves) or single instance.

  • on_app_servers_and_utilities(&block)

    Executes block only on the environment’s application servers and utility servers

  • on_utilities(*utility_names, &block)

    Executes block on utility servers whose names are included in utility_names. utility_names can be passed as an array or as multiple arguments.

    • If called like on_utilities() { ... }, the block will be executed on all utility servers.
    • on_utilities("alpha") { ... } executes the block on all utility servers named “alpha”.
    • on_utilities("a", "b") { ... } is equivalent to on_utilities(%w[a b]) { ... }.

NOTE: Use strings, not symbols, to specify the utility instance names, otherwise your on_utilities block will not be executed. For example: on_utilities(:alpha) will not be executed on a utility instance named "alpha".

  • run(command)

    Executes command as a nonprivileged user (deploy by default).

  • run!(command)

    Executes command as a nonprivileged user. Aborts the deployment if it fails.
    Note: This command can only be used with the CLI and the engineyard gem 2.0. Deployment fails if you try to use run! when deploying your application from the dashboard.

  • sudo(command)

    Executes command as root.

  • sudo!(command)

    Executes command as root. Aborts the deployment if it fails.
    Note: This command can only be used with the CLI and the engineyard gem 2.0. Deployment fails if you try to use sudo! when deploying your application from the dashboard.
  • warning()

    Logs messages to STDERR and allows you to add log-in capabilities to your deploy hooks.

More Information

These other resources might help you:

For more information about ... See ...
Customizing your deployment Customize your Deployment

If you have feedback or questions about this page, add a comment below. If you need help, submit a ticket with Engine Yard Support.

Comments

  • Avatar
    Permanently deleted user

    Hi Courtenay, you might want to log a Support ticket in case there's something wrong here. thanks, kjm

    0
    Comment actions Permalink
  • Avatar
    Permanently deleted user

    The examples in the scripts under the "Shell commands" should be updated with the new style for the API. release_path -> config.release_path, shared_path -> config.shared_path. Thank you!

    0
    Comment actions Permalink
  • Avatar
    Permanently deleted user

    Hi Ryan, thanks for the catch. We'll take a look! kjm

    0
    Comment actions Permalink
  • Avatar
    Permanently deleted user

    Hi Ryan. The doc has been updated. Always appreciate the interaction. Please let us know if you see anything else.

    -diana

    0
    Comment actions Permalink
  • Avatar
    Christopher Haupt

    Hi all,

    We ran in to this recently, so this is a warning/feature request. We have some very large directories in one client's Rails project that are symlinked off of public. Currently when using the engineyard-serverside in production as of this writing, there are various places in the code that make a call into ensure_ownership, which itself checks that all files in the given path (e.g. current) have proper user and group ownership.

    This behavior is killing deploy times on this project, since there are 100's of GB of files symlinked that "find" is trying to analyze and it looks like it is following those links. This method appears to be called at times that are not easily reachable to play games with removing/readding various needed symlinks.

    It would be very cool to add a new callback or other config options to provide a means of filtering what is checked.

    Is there a good way to contribute patches or does anyone have a suggestion (other than "don't put all that content there" :) )

    -Chris

    0
    Comment actions Permalink
  • Avatar
    Permanently deleted user

    @Chris, not to say your feature request might not be a valid one, and apologies for what may sound like a troll, but seriously: don't put all that content there :-)  If you ever need to scale the app horizontally to additional app/web server instances, you'll face the pain of needing to share those files. NFS is fiddly to set up, adds a point of failure, and often doesn't perform/scale very well in a web app workload. IMO your team should strongly consider migrating those files to S3 or the like.

    Sorry to veer a bit off-topic -- I bet Engine Yard staff can follow up privately with some advice for that situation.

    0
    Comment actions Permalink
  • Avatar
    Permanently deleted user

    Hi Chris and Ches, 

    Thanks for the note! I'll check with our serverside specialist and get back to you.

    Thanks, Tasha

    0
    Comment actions Permalink
  • Avatar
    Christopher Haupt

    @ches: No problem. For background for folks who may be in similar situations: This project is a migration for a (very) long-time EY customer who is on Terremark infrastructure that currently relies on NFS for hosting particular kinds of content. We are transitioning the app to AWS and rewriting the portions of the app that had file system dependencies. Since this is such a mission critical app, we are doing things in stages, which include moving new content to S3 and then migrating the legacy content to S3. For initial staging and development testing, we have the large data set locally on EBS volumes. Hence the long deployment times. Once in production, all will be on S3 or similar cloud storage.

    For all: The solution for this was handled in a private ticket, but the approach (since I completely forgot about this and I didn't rewalk the doc): using the advanced customization override files (rather than deploy hooks): https://support.cloud.engineyard.com/entries/20996661-Customize-Your-Deployment#eydeploy 

    This allows us to patch the behavior I mentioned above and simply filter specific directories out of the scan. The deployment time went from 30 minutes to 15 seconds once I pushed that live.

    0
    Comment actions Permalink
  • Avatar
    Permanently deleted user

    Here is a rough draft of the solution we offered Christopher Haupt.

    requirement = Gem::Requirement.create("~>2.3.0")

    unless requirement.satisfied_by?(About.version)

    raise "engineyard-serverside version change detected. Make sure this patch works before deploying to production"end

    In this example, the command below is not altered.

    Here is where you would make your modification to the chown command.

    Methods in the kernel scope here are eval'd into the Deploy object.

    This will then override all calls to ensure_ownership.

    def ensure_ownership(*targets)

      sudo %|find #{targets.join(' ')} \( -not -user #{config.user} -or -not -group #{config.group} \) -exec chown #{config.user}:#{config.group} "{}" +|

    end

    Usually I don't recommend using eydeploy.rb files because of the hassle of maintaining compatibility with each version. I think this case warrants using it.

    The deciding factors for using eydeploy.rb files are that the change is something that is too specific to add a ey.yml configuration option. An option for this would usually cause more problems for customers than it would solve, since altered permissions frequently cause problems. Also, this change is intended to temporarily fix something as they migrate and the customer would rather maintain the deploy consistency themselves.

    The version check at the top is a very useful feature in all eydeploy.rb files. Maintaining this file is up to the customer, but we always do our best to leave internals unchanged in following SemVer. Minor and major version changes can and will significantly alter the internal methods that eydeploy.rb hooks into.

    0
    Comment actions Permalink
  • Avatar
    Matt Scilipoti

    We had some difficulties when attempting to utilize helper methods in our deploy hooks.  I thought EY's recommendation/clarification would be helpful for others:

    Deploy hooks are instance_eval'd into a running copy of the deploy system. Trying to use deploy hooks as if they're part of your application's code will be nearly impossible since you'll be battling with all the context in the deploy code.

    Your best bet is to shell out to a rake task that runs what you need. Deploy hooks should mostly just be used to trigger shell commands wherever possible. This way you can write tests for your rake tasks and scripts and all the hook does is trigger them.

    0
    Comment actions Permalink
  • Avatar
    Permanently deleted user

    I'm Martin Emde and I approve this message.

     

    Thanks for sharing it here. I'll see about putting it in the docs proper.

    0
    Comment actions Permalink
  • Avatar
    Permanently deleted user

    In the shell commands examples, it would be nice to 1. surround the passed shell command with parens, which appears to be required, and 2. use the correct quote delimiters in the example - they are curly quotes, which of course barf.

    0
    Comment actions Permalink
  • Avatar
    Gabe da Silveira

    Using config.all_releases gives me this warning:

    DEPRECATION WARNING: config.all_releases is deprecated. Please use 'config.paths.all_releases' which returns Pathname objects.

    And there is no documentation of config.paths. Makes me wonder what else is missing or deprecated.

    0
    Comment actions Permalink
  • Avatar
    Konstantin Rudy

    Hey,

    Is it possible to access ENV environment variables from after_restart hook?

    Thanks in advance for response.
    Kon

    0
    Comment actions Permalink

Please sign in to leave a comment.

Powered by Zendesk