Deploying Bullet Train to AWS with AppPack
Bullet Train is an open-source SaaS framework for Ruby on Rails.
It provides a starter repo which we can build on to take it from development-ready to production-ready and scalable on AWS. Since the app is already built to run on Heroku, there are very few changes necessary to get it running with AppPack on AWS.
The code for this post lives in ipmb/bullet_train-demo on GitHub.
note: parts of the screencast are sped up for brevity.
Adding healthchecks
AppPack deploys apps to ECS which requires a healthcheck endpoint to determine if the app is healthy before it starts routing traffic to it. To do this, we:
- Add a simple route to that always returns a 
200when the app is running - Disable the SSL redirect on that route
 
That last step is necessary so the healthcheck returns a 200 instead of a 301. The result looks like this:
diff --git a/config/initializers/new_framework_defaults.rb b/config/initializers/new_framework_defaults.rb
index 9ca0a16..3fc9dbf 100644
--- a/config/initializers/new_framework_defaults.rb
+++ b/config/initializers/new_framework_defaults.rb
@@ -23,2 +23,5 @@ Rails.application.config.active_record.belongs_to_required_by_default = true
 # Configure SSL options to enable HSTS with subdomains. Previous versions had false.
-Rails.application.config.ssl_options = {hsts: {subdomains: true}}
+Rails.application.config.ssl_options = {
+    hsts: {subdomains: true},
+    redirect: { exclude: ->(request) { /^\/healthcheck$/.match?(request.path) }}
+}
diff --git a/config/routes.rb b/config/routes.rb
index ab4c7bb..4f3dbd7 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,2 +1,3 @@
 Rails.application.routes.draw do
+  get "/healthcheck", to: proc { [200, {}, ["ok"]] }
   # See `config/routes/*.rb` to customize these configurations.This change may land upstream in Bullet Train soon. See bullet-train-co/bullet_train#632.
Connecting to Redis
AppPack sets up Redis clusters (via Elasticache) using Redis ACLs so they can be shared across multiple apps with different namespaces. This is great for cost savings in development/review app environments where performance is less of a concern. For production you can still use a dedicated cluster if you prefer. We added redis-namespace[1] to support the ACL key prefixes (passed in as the environment variable REDIS_PREFIX):
- Added 
redis-namespaceto theGemfile - Ran 
bundle install - Edited 
config/initializers/sidekiq.rb - Edited 
config/cable.yml 
diff --git a/Gemfile.lock b/Gemfile.lock
index 0711960..7b4cc13 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -457,2 +457,4 @@ GEM
     redis (4.8.0)
+    redis-namespace (1.10.0)
+      redis (>= 4)
     regexp_parser (2.6.1)
@@ -614,2 +616,3 @@ DEPENDENCIES
   redis (~> 4.0)
+  redis-namespace
   rqrcode
diff --git a/config/cable.yml b/config/cable.yml
index fc14aab..5ca1c58 100644
--- a/config/cable.yml
+++ b/config/cable.yml
@@ -9,3 +9,3 @@ production:
   url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
-  channel_prefix: bullet_train_apppack_demo_production
+  channel_prefix: <%= ENV['REDIS_PREFIX'] || '' %>bullet_train_apppack_demo_production
   driver: :ruby
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index e1129e8..b5f0738 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -1,3 +1,3 @@
 redis_conn = proc {
-  Redis.new(
+  redis = Redis.new(
     url: ENV["REDIS_URL"] || "redis://localhost:6379",
@@ -6,2 +6,7 @@ redis_conn = proc {
   )
+  if ENV["REDIS_PREFIX"]
+    Redis::Namespace.new(ENV["REDIS_PREFIX"], :redis => redis)
+  else
+    redis
+  end
 }Storing file uploads in S3
This is already built-in, but assumes the presence of static access keys in the environment which aren't needed on ECS and our environment variable name does not match. That change looks like this:
diff --git a/config/storage.yml b/config/storage.yml
index 8426d2c..143f7b9 100644
--- a/config/storage.yml
+++ b/config/storage.yml
@@ -10,5 +10,5 @@ amazon:
   service: S3
-  access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %>
-  secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %>
-  bucket: <%= ENV['AWS_S3_BUCKET'] %>
+  # access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %>
+  # secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %>
+  bucket: <%= ENV['PRIVATE_S3_BUCKET_NAME'] %>
   region: <%= ENV['AWS_S3_REGION'] %>Avoiding database access during the build
The AppPack build pipeline is isolated from the app's live resources (e.g., database, Redis, etc.). During the Rails asset pipeline, the app tries to connect to the database and times out due to the isolation. We can fix this by telling it to connect to an in-memory SQLite database during the build process. That requires adding the sqlite3 gem to the project and changing the database URL based on the precense of a CI environment variable:
diff --git a/Gemfile b/Gemfile
index bf9d2bb..413b596 100644
--- a/Gemfile
+++ b/Gemfile
@@ -178,0 +179 @@ gem "redis-namespace"
+gem "sqlite3"
diff --git a/Gemfile.lock b/Gemfile.lock
index 7b4cc13..2ef534d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -524,0 +525,3 @@ GEM
+    sqlite3 (1.6.0)
+      mini_portile2 (~> 2.8.0)
+    sqlite3 (1.6.0-x86_64-linux)
@@ -624,0 +628 @@ DEPENDENCIES
+  sqlite3
diff --git a/config/database.yml b/config/database.yml
index 2a88eea..d548a82 100644
--- a/config/database.yml
+++ b/config/database.yml
@@ -85,0 +86,3 @@ production:
+  <% if ENV["CI"] %>
+  url: "sqlite3::memory:"
+  <% else %>
@@ -86,0 +90 @@ production:
+  <% end %>Ready to get on the fast track to AWS?
AppPack is the easiest way to deploy your Rails apps to AWS.
          AppPack deployment
Prior to creating your application, you'll need to complete the Initial Setup tutorial and create a Postgres database and Redis instance in your cluster.
You can view a screencast of the entire app creation process above or continue reading for additional details.
To create the application, we'll run:
apppack create app bullet-trainBe sure to:
- Set the healthcheck endpoint to 
/healthcheck - Enable the database add-on
 - Enable the Redis add-on
 - Enable the private S3 bucket add-on
 
Once the app is created, we'll set a few config variables the app is expecting:
apppack -a bullet-train config set AWS_S3_REGION=us-east-2  # replace with your AWS region
apppack -a bullet-train config set SECRET_BASE_KEY=your-secret-base-key  # replace with random string
apppack -a bullet-train config set BASE_URL=https://bullet-train.cluster.apppack.rocks  # replace with app's URLThen we can kick off the first build (future builds will happen automatically on pushes to the repo):
apppack -a bullet-train build start --watchYou can also view your build in the web interface:
        
          
Finally, we're ready to start working with our app. To open it in your default browser, run:
apppack -a bullet-train openAnd there you have it. Your Bullet Train site is live. With AppPack, there are no servers to manage or maintain. Everything runs in your AWS account using AWS-native managed services.
Sidekiq 7.0 removed support for namespaces. This breaks compatibility with AppPack's Redis add-on, but because AppPack runs in your own AWS account, there's an easy work-around. If you add your own Redis user with an ACL of
on ~* &* +@alland manually set theREDIS_URLon your app, you'll have access to the full keyspace. In this scenario, you'll be responsible for managing isolation on each Redis instance. ↩︎