# Deploying a Rails Application with AWS Copilot
This post outlines deploying a Rails application using AWS Copilot, a CLI tool designed to provide a Heroku-like experience within the AWS ecosystem. Before proceeding, it’s recommended to familiarize yourself with the basics by following the “Deploy your first application” guide in the Copilot documentation.
I used the Jumpstart Rails project template for this deployment. The steps and configurations discussed here are specific to this template but might be adaptable to other Rails applications.
Important Notes:
- Each
copilot
command executes CloudFormation operations in the background. Inspecting the CloudFormation Stacks in the AWS console can be insightful if you encounter issues. copilot
commands can be time-consuming.
# Setting Up the Application
First, log into the AWS account and set up the application and environment using the following commands:
# Log into the AWS account (using SSO for this deployment)
aws configure sso
# Initialize the application repository
copilot init \
-a rails-app \
-t "Load Balanced Web Service" \
-n web \
-d ./Dockerfile.production
# Create a test/development environment
copilot env init \
--name dev \
--app rails-app \
--default-config
This step does not deploy the application but sets up the necessary
configuration within your repository’s copilot/
directory.
# Database Configuration
Next, allocate the database as a
storage
unit:
copilot storage init \
-n webdb \
-t Aurora \
-w web \
-l environment \
--engine PostgreSQL \
--initial-db railsapp
For the application to access database credentials, modify the manifest.yml
file as follows:
network:
vpc:
security_groups:
- from_cfn: ${COPILOT_APPLICATION_NAME}-${COPILOT_ENVIRONMENT_NAME}-webdbSecurityGroup
secrets:
DATABASE_JSON:
from_cfn: ${COPILOT_APPLICATION_NAME}-${COPILOT_ENVIRONMENT_NAME}-webdbAuroraSecret
# Application Configuration
AWS provides DATABASE_JSON
instead of the typical DATABASE_URL
. Adjust the
database.yml
to parse these credentials:
production:
<<: *default
# Parse DATABASE_JSON if available
<% if ENV['DATABASE_JSON'] %>
<% require 'json' %>
<% db_config = JSON.parse(ENV['DATABASE_JSON']) %>
username: <%= db_config['username'] %>
password: <%= db_config['password'] %>
host: <%= db_config['host'] %>
port: <%= db_config['port'] %>
database: <%= db_config['dbname'] %>
<% else %>
url: <%= ENV['DATABASE_URL'] %>
<% end %>
In your Dockerfile.production
, include instructions for precompiling assets
and starting the Rails server:
RUN SECRET_KEY_BASE=dummy-staging-key bin/rails assets:precompile
CMD ["bin/rails", "server", "-b", "0.0.0.0"]
Set environment variables for production in your manifest.yml
:
variables:
RAILS_ENV: production
SECRET_KEY_BASE: <value from `rails secret`>
# Service Configuration
Configure the application as a “Load Balanced Web Service” to enable command execution in the container, essential for tasks like database migration.
exec: true
Define routing and health check settings:
http:
path: '/'
healthcheck: '/up'
Since SSL support isn’t provided by default, turn off SSL enforcement in
config/environments/production.rb
:
config.force_ssl = false
# Deploying the Application
Deploy the application and database using the following commands:
copilot deploy \
--deploy-env \
--env dev \
--name web
# Run database migrations post-deployment
copilot svc exec \
--command "bin/rails db:migrate" \
-n web
# View application logs
copilot svc logs
# Display deployment information
copilot svc show
You should have a URL in the show
command that will expose your app to the
public Internet.
We did it! We’ve deployed our application.
This has been the best application tool to deploy into the AWS ecosystem. This is, however, the longest time it has taken me to deploy a hello world Rails application with compromises – i.e., SSL, one of the tasks, etc.
How can I help make this experience better for others after me? I have a blog post to write about the incident. I do want the experience to be more flawless. There are a lot of gothyas and “didn’t you reads” that have been discoverable but disappointing.
Note: To clean it all up
copilot app delete
.
# Additional Resources
- Deploy Django App: Offers insights into web app deployment in the cloud, including database configuration.
- SSL Support Discussion
# Update
I was later informed via a
chat thread
with the team, that I can get SSL using Request-Driven Web Service
.
copilot init \
-a rails-app \
-t "Request-Driven Web Service" \
-n web \
-d ./Dockerfile.production
Note: When setting up the database the instructions are bit different, but the cli will tell you what to add.
The issue that I was running into of not being able database migrations can be
solved with
copilot task run
.
copilot task run \
--command "bin/rails db:migrate" \
--dockerfile Dockerfile.production \
--env dev \
--app rails-app \
--follow
However, I found a bug with task run
. When it builds the docker image, it uses
the host architecture, it is not building for the target architecture. I changed
my command to use the latest image of the app I just deployed, using
docker images
.
copilot task run \
--command "bin/rails db:migrate" \
--image <ecr host>/rails-app/web \
--env dev \
--app rails-app \
--follow
This did not work because the task run
does not assume the environment
variables and secrets from the services. This would include RAILS_ENV
and the
DATABASE_JSON
.
This does not appear to be a viable way for me.