I've been using ember-cli to develop watch.canary.io and I'm deploying it to S3. I started by just manually copying the files into my bucket, but I finally decided to set up a real automated deployment process. Here's how I set up grunt to allow for easy command line based deployment.

First I looked for a grunt extension for deploying to S3. I found several, but grunt-s3 seems to be the most active, so I went with that. So then I installed grunt and grunt-s3 into my project.

npm install --save-dev grunt
npm install --save-dev grunt-s3

Next I created a file called aws-keys.json in the root of my project to hold my S3 credentials and the name of the bucket I want to deploy to.

{
  "AWSAccessKeyId": "AK.....",
  "AWSSecretKey": "sooooper-secret-stuff",
  "bucket":"watch.canary.io"
}

Since I don't want this info to be puclicly available in the GitHub repo, I added this file to .gitignore.

echo "aws-keys.json" >> .gitignore

Finally I created Gruntfile.js to tell grunt how what to do.

module.exports = function(grunt) {

  grunt.initConfig({

    // Load the S3 credentials from aws-keys.json
    aws: grunt.file.readJSON('aws-keys.json'),

    // Pass config options to 'grunt-s3'
    s3: {
      options: {
        key: '<%= aws.AWSAccessKeyId %>',
        secret: '<%= aws.AWSSecretKey %>',
        bucket: '<%= aws.bucket %>',
        access: 'public-read',
        headers: {
          // Two Year cache policy (1000 * 60 * 60 * 24 * 730)
          "Cache-Control": "max-age=630720000, public",
          "Expires": new Date(Date.now() + 63072000000).toUTCString()
        }
      },
      prod: {
        upload: [
          {
            src: 'dist/index.html',
            dest: 'index.html'
          },
          {
            src: 'dist/assets/*',
            dest: 'assets/'
          }
        ]
      }
    } // end s3

  }); // end grunt.initConfig

  // tell grunt to load and use the grunt-s3 extension
  grunt.loadNpmTasks('grunt-s3');

  // Default task(s).
  grunt.registerTask('default', ['s3']);

};

Now I can run grunt s3 (or just grunt since I set the default task) and all the compiled project files in dist will be uploaded to S3. At this point the files are allowed to be cached for 2 years, which means that people will need to do a hard refresh to see any updates. This is less than ideal.

To fix the hard reload issue I'm going to add asset fingerprinting, so that the paths to asset files will be updated any time that the underlying assets change. This will allow me to still use a long cache timeout on the assets, and then just reduce the timeout for index.html so that visitors almost always get a fresh copy of index.html, which will always be pointing to the latest version of the assets. grunt-hashres is a grunt extension that can handle renaming asset files with a fingerprint, and can also rewrite any references to those files in index.html.

So, next I installed grunt-hashres.

npm install --save-dev grunt-hashres

Then I updated Gruntfile.js to use and configure that extension.

module.exports = function(grunt) {

  grunt.initConfig({
    aws: grunt.file.readJSON('aws-keys.json'),
    s3: {
      // previously discussed S3 config goes here 
    }, // end s3

    hashres: {
      prod: {
        // files to be fingerprinted
        src: [
          'dist/assets/app.js',
          'dist/assets/vendor.css',
          'dist/assets/app.css'
        ],
        // file that needs references rewritten
        dest: 'dist/index.html',
      }
    } // end hashres
  });

  grunt.loadNpmTasks('grunt-s3');
  grunt.loadNpmTasks('grunt-hashres');

  // Default task(s).
  grunt.registerTask('default', ['hashres','s3']);

};

Now the only thing left to do is to update the grunt-s3 config to set a much lower cache timeout policy for index.html. This can be done easily by passing an options block to the portion of the upload config for index.html. The block for dist/assets/* will just use the default timeout that I set at the top of the s3 configuration section (2 years).

        // ...
        upload: [
          {
            src: 'dist/index.html',
            dest: 'index.html',
            options: {
              headers: {
                // 1 minute cache policy (1000 * 60)
                "Cache-Control": "max-age=60000, public",
                "Expires": new Date(Date.now() + 60000).toUTCString()
              }
            }
          },
          {
            src: 'dist/assets/*',
            dest: 'assets/'
          }
        ]
        // ...

Now whenever I'm ready to deploy a new release it's as easy as:

ember build
grunt

ember build works just like normal, aggregating/compling the JS, CSS and SASS files into production ready files. Then grunt will fingerprint the assets files, rewrite the references to them in index.html and upload all of the production files to S3.

You can see my full Gruntfile.js here