Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save JeremyC-za/edb758525d5d98870f009dc1bba54d15 to your computer and use it in GitHub Desktop.
Save JeremyC-za/edb758525d5d98870f009dc1bba54d15 to your computer and use it in GitHub Desktop.

Deploying a basic Ruby on Rails app with Postgres Database to Microsoft Azure

This was written on 8 November, 2019. Some of the issues mentioned below were discovered and resolved with the help of Microsoft support staff, and will hopefully be fixed in the near future.

Overview

The steps below are mostly taken from this tutorial, Deploying your Rails + PostgreSQL app on Microsoft Azure, but have also been influenced by these two other tutorials: Create a Ruby on Rails App in App Service on Linux and Build a Ruby and Postgres app in Azure App Service on Linux.

The descriptions of each step are my own personal understanding. I'm not an Azure expert, so they may not be correct 100% of the time.

Rails Version Note: The versions used are important since Azure doesn't support the latest versions. If your Rails App contains a package.json file, you will need to delete it in order to be able to deploy to Azure. This is because Azure uses the presence of that package.json file to flag your app as a node.js website, and not a Ruby website, and generates the incorrect deployment script.

Another Rails Version Note: Even if you've deleted the package.json file mentioned above, and the app still doesn't run, it's probably because of the Rails version used. I'm not sure where the boundary is, but I tried using version 5.2.3 and got errors, but using 5.1.6 worked.

Bundler Version Note: Azure also does not suport the latest version of Bundler. If you are running Bundler 2, you will need to downgrade bundler version for your app - version 1.17.2 works. You can check the version of Bundler used at the bottom of your Gemfile.lock file. Worst case scenario is you can just edit the version in there to get the deploy to work, although that's probably not advised since you shouldn't manually edit that file.

Step 1: Create a simple, brand new app

rails new example-app --database=postgresql
cd example-app
bundle
git add .
git commit -m "Initial commit"

This creates a brand new app called example-app with the database set to Postgres. Afterwards the bundle command installs all of the required gems.

Step 2: Create a User model

rails generate scaffold User name:string email:string
rails db:create
rails db:migrate
git add .
git commit -m "Implement User model, views and controllers"

This generates the User model, controller, and views. It then creates the databases locally so we can run our app through localhost.

Step 3: Check that the app works

rails s

Start the app using the above command and, in your browser, go to http://localhost:3000/users. The page should load properly and give you the option to add a new user.

Step 4: Download and log in with the Azure CLI

You can download the CLI here: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest You must then log in with the CLI using az login. This should open your browser and prompt you to login. Once you're done, your terminal should have a JSON response in it, but you can pretty much ignore that.

Step 5: Set up a Deployment User for Azure Deployments

Users must be authorized with Azure to be able to make deployments. Using the CLI, enter the following command az webapp deployment user set --user-name <username> --password <password>. Note: usernames cannot contain the @ character.

Step 6: Set up a Resource Group for your app

All resources in Azure must be contained in a resource group, which helps keeps things organised. You can create one by entering az group create --location westeurope --name exampleResourceGroup. I couldn't find a command to view a full list of available locations, but you can paste the command with an invalid string (eg. 'sadhjfajksdg'), and the error message response will list the valid ones for you.

Step 7: Create an App Service Plan

An App Service Plan consists of the underlying virtual machines that will host the Azure App Services. The App Service Plan defines the region of the physical server where your app will be hosted on and the amount of storage, RAM, and CPU the physical servers will have. There are several different tiers, for different usage requirements.

You can create an App Service Plan using the following command: az appservice plan create --name exampleAppServicePlan --resource-group exampleResourceGroup --is-linux. This creates a service plan inside of the resource group we just created, specified by the --resource-group flag. It also specifies that we want the service plan to run on a linux server using the --is-linux flag.

More information about this can be found here: https://docs.microsoft.com/en-us/cli/azure/appservice/plan?view=azure-cli-latest.

Step 8: Create the Webapp and store the Deployment URL

The Webapp is the actual webapp that will run on the infrastructure defined by the App Service Plan. You can create one with az webapp create --resource-group exampleResourceGroup --plan exampleAppServicePlan --name <unique_name> --runtime "RUBY|2.6.2" --deployment-local-git. This will create the Webapp in the specified resource group, for the specified App Service Plan. The --runtime flag tells the Webapp what kind of app we're going to deploy. You can view the available runtimes with az webapp list-runtimes --linux. The --deployment-local-git flag just tells that app that we'll deploy code to it from a local git repository.

More information about this can be found here: https://docs.microsoft.com/en-us/cli/azure/webapp?view=azure-cli-latest#az-webapp-create.

Once the command has run successfully, you should get a JSON response. Near the top of the response, look for the "deploymentLocalGitUrl" key. This is the url we will use to deploy to our app. It should look like this: https://<deployment_username>@<unique_name>.scm.azurewebsites.net/<unique_name>.git

It's probably also a good idea to enable logging for your app at this point: az webapp log config --name <unique_name> --resource-group exampleResourceGroup --web-server-logging filesystem

Step 9: Deploy the Webapp to Azure

Before we deploy to Azure, we'll need to set up a git remote that we can push to. You can do this with git remote add azure <deployment_url>. Once that's done, deploying to Azure is done using git push azure master. It should prompt you for your password that you entered when setting up the deployment user in Step 5, so enter that. That should work as expected, but if you run into any trouble, try restarting the web app with az webapp restart --name <unique_name> --resource-group exampleResourceGroup

This is where you might encounter errors due to the versions mentioned in the Overview. Make sure you see remote: Generating deployment script for Ruby Web Site when you deploy. If your app contains a package.json file, you might see remote: Generating deployment script for node.js Web Site, which will cause the deployment to fail. If the correct deployment script is generated, but the deployment fails with messages remote: bundler failed; remote: To install the missing version, run 'gem install bundler:2.0.2', this means your version of Bundler used in your app is not supported by Azure. I don't know specifically which versions are or aren't supported, but 1.17.2 works.

Step 10: Create a Postgres Database

Next we'll create the database that our app will connect to: az postgres server create --name <unique_db_name> --resource-group exampleResourceGroup --location "North Europe" --admin-user <db_username> --admin-password <db_password> --sku-name B_Gen5_1. The --admin-user and --admin-password don't need to be the same as the deployment user, but may as well be if there's only one developer working with Azure. The --sku-name field determines the size of the database, with B_Gen5_1 being the smallest.

More information about the command and optional flags can be found here: https://docs.microsoft.com/en-us/cli/azure/postgres/server?view=azure-cli-latest#az-postgres-server-create.

Step 11: Set up firewall rules to access the database

To access the database, we need to configure the firewall to allow certain IP addresses in. For now, we can just allow everything, but an actual production database would need to be carefully configured - I don't know enough about the subject to know what a proper configuration would look like. az postgres server firewall-rule create --name allIP --server <unique_db_name> --resource-group exampleResourceGroup --start-ip-address 0.0.0.0 --end-ip-address 255.255.255.255

Step 12: Check database access

Check that the firewall rule is configured correctly by trying to connect to the database from your terminal: psql -U <db_username>@<unique_db_name> -h <unique_db_name>.postgres.database.azure.com -p 5432 -d postgres. If if lets you in, good, you can quit with \q.

If you get this error: dyld: Library not loaded: /usr/local/opt/readline/lib/libreadline.7.dylib, just do this:

cd /usr/local/opt/readline/lib 
ln -s libreadline.8.0.dylib libreadline.7.dylib

I don't really know what it does, but it was taken from here https://stackoverflow.com/questions/54261455/library-not-loaded-usr-local-opt-readline-lib-libreadline-7-dylib and it fixed the problem for me.

Step 13: Configure the app to connect to a production database

The default Rails app is not configured for a production database. To do this, open <app_root>/config/database.yml. By default, the production settings (at the bottom of the file) should look something like this:

production:
  <<: *default
  database: example-app_production
  username: example-app
  password: <%= ENV['EXAMPLE-APP_DATABASE_PASSWORD'] %>

Replace the settings so they look like this:

production:
  <<: *default
  host: <%= ENV['AZURE_DB_HOST'] %>
  port: 5432
  database: <%= ENV['AZURE_DB_DATABASE'] %>
  username: <%= ENV['AZURE_DB_USERNAME'] %>
  password: <%= ENV['AZURE_DB_PASSWORD'] %>

Commit the changes once finished. Don't worry about pushing to Azure, we'll do that later.

Step 14: Set up Environment Variables

Now we need to create the environment variables referenced in the production database settings created above. These will need to be configured locally on your computer, and on the Azure webapp server. To start with the Azure server: az webapp config appsettings set --name <unique_name> --resource-group exampleResourceGroup --settings AZURE_DB_HOST="<unique_db_name>.postgres.database.azure.com" AZURE_DB_DATABASE="postgres" AZURE_DB_USERNAME="<db_username>@<unique_db_name>" AZURE_DB_PASSWORD="<db_password>"

To set them locally, use nano ~/.bash_profile and add:

export AZURE_DB_HOST="<unique_db_name>.postgres.database.azure.com"
export AZURE_DB_DATABASE="postgres"
export AZURE_DB_USERNAME="<db_username>@<unique_db_name>"
export AZURE_DB_PASSWORD="<db_password>"

Once done, hit ctrl-x to quit, and y and then enter to save. Your terminal may need to be restarted for these new variables to be recognised. Alternatively, you could just enter the same commands into your terminal to set the environment variables temporarily.

Step 15: Migrate the production database

Now that we're connected to the database, we need to migrate it to reflect the schema of our app: rails db:migrate RAILS_ENV=production.

Step 16: Final Azure config

Generate a rails secret using rails secret and set it as an evironment variable: az webapp config appsettings set --name <unique_name> --resource-group exampleResourceGroup --settings RAILS_MASTER_KEY="<rails_secret>" SECRET_KEY_BASE="<rails_secret>" RAILS_SERVE_STATIC_FILES="true" ASSETS_PRECOMPILE="true"

Step 17: Final Push

Now that everything is set up, we just need to quickly precompile our assets manually: rake assets:precompile. Commit that, and then push the application to azure: git push azure master.

Any migrations or changes to the database in the future must be run manually using rails db:migrate RAILS_ENV=production. They will not be run automatically on deploy like they are on AWS.




Quick Reference for Testing

For if you're repeating these steps during testing, like I am.
Paste this into whatever text editor you use, then just replace all the placeholder names at once to speed things up

rails _5.1.6_ new <app_name> --database=postgresql
cd <app_name>
bundle
git add .
# Delete package.json and change Gemfile.lock BUNDLED WITH version 
git commit -m "Initial commit"
rails generate scaffold User name:string email:string
rails db:create
rails db:migrate
git add .
git commit -m "Implement User model, views and controllers"
az group create --location westeurope --name <resourceGroupName>

az appservice plan create --name <appServicePlanName> --resource-group <resourceGroupName> --is-linux

az webapp create --resource-group <resourceGroupName> --plan <appServicePlanName> --name <webAppName> --runtime "RUBY|2.6.2" --deployment-local-git

az webapp log config --name <webAppName> --resource-group <resourceGroupName> --web-server-logging filesystem
git remote add azure https://<deploymentUserName>@<webAppName>.scm.azurewebsites.net/<webAppName>.git
git push azure master
az postgres server create --name <databaseName> --resource-group <resourceGroupName> --location "North Europe" --admin-user <databaseUsername> --admin-password <databasePassword> --sku-name B_Gen5_1

az postgres server firewall-rule create --name allIP --server <databaseName> --resource-group <resourceGroupName> --start-ip-address 0.0.0.0 --end-ip-address 255.255.255.255
# In database.yml
production:
  <<: *default
  host: <%= ENV['AZURE_DB_HOST'] %>
  port: 5432
  database: <%= ENV['AZURE_DB_DATABASE'] %>
  username: <%= ENV['AZURE_DB_USERNAME'] %>
  password: <%= ENV['AZURE_DB_PASSWORD'] %>
az webapp config appsettings set --name <webAppName> --resource-group <resourceGroupName> --settings AZURE_DB_HOST="<databaseName>.postgres.database.azure.com" AZURE_DB_DATABASE="postgres" AZURE_DB_USERNAME="<databaseUsername>@<databaseName>" AZURE_DB_PASSWORD="<databasePassword>"

export AZURE_DB_HOST="<databaseName>.postgres.database.azure.com"
export AZURE_DB_DATABASE="postgres"
export AZURE_DB_USERNAME="<databaseUsername>@<databaseName>"
export AZURE_DB_PASSWORD="<databasePassword>"
rails db:migrate RAILS_ENV=production
rails secret
az webapp config appsettings set --name <webAppName> --resource-group <resourceGroupName> --settings RAILS_MASTER_KEY="<rails_secret>" SECRET_KEY_BASE="<rails_secret>" RAILS_SERVE_STATIC_FILES="true" ASSETS_PRECOMPILE="true"
rake assets:precompile
git add .
git commit -m "Database settings and precompiled assets"
git push azure master
@JeremyC-za
Copy link
Author

do you experience error on deploying it to azure like

remote: Node.js versions available on the platform are: 4.4.7, 4.5.0, 4.8.0, 6.2.2, 6.6.0, 6.9.3, 6.10.3, 6.11.0, 6.17.1, 8.0.0, 8.1.4, 8.2.1, 8.8.1, 8.9.4, 8.11.2, 8.12.0, 8.15.1, 8.16.2, 9.4.0, 10.1.0, 10.10.0, 10.14.2, 10.17.0, 12.13.0.
remote: ENOENT: no such file or directory, open '/opt/nodejs/12.13.0/npm.txt'

I don't remember ever getting the "no such file or directory" error, but the fact that it's listing Node.js versions means it's probably generating the incorrect deployment script. Make sure when you've read the Rails Version notes under the overview. If you have already, then I don't think I can help you. Good luck.

@delacruzjames
Copy link

Thanks, Jeremy, I need to update my ruby and rails version it works fine now. the issue I'm encountering right now is on http port 8080

Do you encounter something like this

ERROR - Container <my_app_name>_0_1de9fbae for site <my_app_name> has exited, failing site start
ERROR - Container <my_app_name>_0_1de9fbae didn't respond to HTTP pings on port: 8080, failing site start. See container logs for debugging.

@JeremyC-za
Copy link
Author

Thanks, Jeremy, I need to update my ruby and rails version it works fine now. the issue I'm encountering right now is on http port 8080

Do you encounter something like this

ERROR - Container <my_app_name>_0_1de9fbae for site <my_app_name> has exited, failing site start
ERROR - Container <my_app_name>_0_1de9fbae didn't respond to HTTP pings on port: 8080, failing site start. See container logs for debugging.

I do remember getting those errors, but the cause was always somewhere else. I only got that when something failed in the previous steps.

@studix
Copy link

studix commented May 25, 2020

I made it through the error caused by the package.json. Now the app is deployed but cannot be started. I encounter the same errors as @delacruzjames. The container logs do not help.

.._docker.log:

2020-05-20 18:53:36.737 ERROR - Container xy for site tournaments-app has exited, failing site start
2020-05-20 18:53:36.760 ERROR - Container xy didn't respond to HTTP pings on port: 8080, failing site start. See container logs for debugging.
2020-05-20 18:53:36.786 INFO  - Stoping site tournaments-app because it failed during startup.

..default_docker.log:

2020-05-20T18:53:35.862298997Z 	from bin/rails:3:in `<main>'

The app runs fine locally with production environment. Any advice how I can debug the problem?

@delacruzjames
Copy link

@studix Steps that I did

  1. verify ruby version to your local and to the azure
  2. try to run it locally using prod environment (see if there is an error on the logs)

@studix
Copy link

studix commented May 27, 2020

@delacruzjames: The app runs locally using prod environment and ruby version is ok (both 2.3.8)

If someone wants to follow the issue I have I posted a question in Microsoft Docs Q&A:
https://docs.microsoft.com/en-us/answers/questions/29678/diagnose-container-start-issues-in-azure-app-servi.html

@joaku
Copy link

joaku commented Jun 6, 2020

do you experience error on deploying it to azure like

remote: Node.js versions available on the platform are: 4.4.7, 4.5.0, 4.8.0, 6.2.2, 6.6.0, 6.9.3, 6.10.3, 6.11.0, 6.17.1, 8.0.0, 8.1.4, 8.2.1, 8.8.1, 8.9.4, 8.11.2, 8.12.0, 8.15.1, 8.16.2, 9.4.0, 10.1.0, 10.10.0, 10.14.2, 10.17.0, 12.13.0.
remote: ENOENT: no such file or directory, open '/opt/nodejs/12.13.0/npm.txt'

I have the same problem. Any idea how to fix it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment