Connect To Production Rails Console on AWS / Flightcontrol

When you've built Rails apps on Heroku for over a decade, there are lot of niceties that you take for granted. I recently started working on a Rails project deployed to AWS via Flightcontrol. I've found a lot of aspects of Flightcontrol to be smooth and straighforward. One of the rough edges that I ran into was something seemingly small, but quite important. Connecting to a production Rails console.

With Heroku that is as easy as:

$ heroku run rails console --app my_app

With a Rails app that is deployed to AWS via Flightcontrol, it's not so straightforward. There isn't a Flightcontrol CLI with these kinds of conveniences built in. That makes sense though, Flightcontrol isn't trying to be a PaaS like Heroku, it is making it easier for us to deploy and manage our app on AWS infrastructure.

AWS has a CLI. That's what we are going to use to run rails console, but it's not going to be that easy. Just like Git has porcelain and plumbing commands, we can think of Heroku as having been our high-level porcelain commands, that is, intuitive, user-centric, workflow-focused commands. Whereas the AWS CLI provides the low-level plumbing commands. They are less intuitive, service-oriented, and developer/machine-centric.

That's okay though. They'll just take a little more work and understanding. Let's dive in.

Assumptions

First off, I'm assuming you already have the AWS CLI installed and that you are authenticated with a user that has appropriate permissions for things like SSH'ing into an ECS container. For example, my IAM Identity Center user belongs to a group that has PowerUserAccess permissions set for my application.

Installing the AWS CLI and setting up users and permissions for authentication are both better covered elsewhere and out of the scope of this post.

Session Manager Plugin

Though this won't come into play until a few steps down the line, we will need the Session Manager plugin for the AWS CLI in order to SSH into our ECS container.

Let's get that out of the way and install it now following the OS-specific instructions outlined in Install the Session Manager plugin for the AWS CLI.

Container Details

We need to identify a couple key details about the ECS container that our Rails app is running on. These will be needed to establish an SSH connection.

We need:

  • Container region
  • Container/Cluster ID
  • Container Task ID

Let's list the ECS Clusters this AWS profile knows about:

$ aws ecs list-clusters

{
    "clusterArns": [
        "arn:aws:ecs:us-east-2:123456789012:cluster/fc-rails-app-abc123-4567efgh"
    ]
}

We can tell this is the cluster we are interested in because of the Container/Cluster ID, fc-rails-app-... (flightcontrol rails app). Grab that Container/Cluster ID value (fc-rails-app-abc123-4567efgh) and hang on to it.

Toward the beginning of the Cluster ARN we see the region as well -- us-east-2.

Now, let's figure out the Task ID for our cluster.

$ aws ecs list-tasks --region us-east-2 --cluster fc-rails-app-abc123-4567efgh

{
    "taskArns": [
        "arn:aws:ecs:us-east-2:123456789012:task/fc-rails-app-abc123-4567efgh/e8bc622caa23e172c66d84db70f5c33b"
    ]
}

The ID at the end of that Task ARN is the Task ID: e8bc622caa23e172c66d84db70f5c33b.

Those are the details we needed to figure out about the container. Now let's connect.

SSH into the ECS Container

The Flightcontrol docs explain how to SSH into the container using the above three pieces of info.

$ aws ecs execute-command \
  --region <region> \
  --cluster <cluster-id> \
  --container <cluster-id> \
  --task <task-id> \
  --interactive \
  --command "/bin/sh"

We can fill in the values we found and connect:

$ aws ecs execute-command \
  --region us-east-2 \
  --cluster fc-rails-app-abc123-4567efgh \
  --container fc-rails-app-abc123-4567efgh \
  --task e8bc622caa23e172c66d84db70f5c33b \
  --interactive \
  --command "/bin/sh"

We should now be connecting to the container via a bare bones shell session. But try doing something like ruby -v (ruby won't be found) and you'll see we have a problem.

Let's exit from that session and connect via bash instead.

$ aws ecs execute-command \
  --region us-east-2 \
  --cluster fc-rails-app-abc123-4567efgh \
  --container fc-rails-app-abc123-4567efgh \
  --task e8bc622caa23e172c66d84db70f5c33b \
  --interactive \
  --command "/bin/bash"

That's better, we get some terminal colors and ruby -v works. In fact, if we run which ruby, we'll get a hint as to what the issue with /bin/sh was.

$ which ruby
/root/.rbenv/shims/ruby

rbenv is being used, so something was missing or unset in the /bin/sh session that is available in this /bin/bash session.

You can take a look at both the /root/.bashrc and the /root/.profile files if you want. These are the sorts of files that are executed when initializing a new bash session. You'll notice in the .profile file that rbenv is being initialized and the PATH set.

# ...

PATH=$NIXPACKS_PATH:$PATH
eval "$(~/.rbenv/bin/rbenv init -)"
PATH=$HOME/.rbenv/bin:$PATH
PATH=/usr/local/rvm/rubies/ruby-3.2.2/bin:/usr/local/rvm/gems/ruby-3.2.2/bin:/usr/local/rvm/gems/ruby-3.2.2@global/bin:$PATH

These are the pieces of our environment that make Ruby available and give our Rails app what it needs to run.

Production Rails Console

With this bash SSH session, we are able to connect to the production Rails console. Try it.

$ bin/rails console
Loading production environment (Rails 7.2.1)
my-app(prod)> Rails.env
=> "production"
my-app(prod)>

It'd really be nice if we could connection to the Rails console in a single command from our local machine. Let's take this a step further.

$ aws ecs execute-command \
  --region us-east-2 \
  --cluster fc-rails-app-abc123-4567efgh \
  --container fc-rails-app-abc123-4567efgh \
  --task e8bc622caa23e172c66d84db70f5c33b \
  --interactive \
  --command "/bin/bash -c 'bin/rails console'"

The Session Manager plugin was installed successfully. Use the AWS CLI to start a session.


Starting session with SessionId: ecs-execute-command-a2492512b75c2e7b91bdd7a83b197059
/usr/bin/env: ‘ruby’: No such file or directory


Exiting session with sessionId: ecs-execute-command-a2492512b75c2e7b91bdd7a83b197059.

We're close, but this is missing Ruby. That's because without logging in, the profile (.profile) isn't being sourced. Let's source it manually before starting the console.

$ aws ecs execute-command \
  --region us-east-2 \
  --cluster fc-rails-app-abc123-4567efgh \
  --container fc-rails-app-abc123-4567efgh \
  --task e8bc622caa23e172c66d84db70f5c33b \
  --interactive \
  --command "/bin/bash -c 'source /root/.profile && bin/rails console'"

The Session Manager plugin was installed successfully. Use the AWS CLI to start a session.


Starting session with SessionId: ecs-execute-command-a2492512b75c2e7b91bdd7a83b197059
Loading production environment (Rails 7.2.1)
my-app(prod)>

That's it. We're connected. This is a production app with production data, so be appropriately careful as you access and modify data.

An exercise for the reader is to then pull this one-liner out into a bin script or other more ergonomic way of issuing the command from your local environment.


If you've gotten this far, thanks for reading! Is there a better way to do this? Let me know.

I've been exploring alternatives to Heroku for deploying Rails apps. What do your Rails app deployments look like? What platform should I evaluate next? Let me know. I'm on Bluesky and Twitter, or email me.

Tell us about your project

We build good software through good partnerships. Reach out and we can discuss your business, your goals, and how VisualMode can help.