<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[tasty_var]]></title><description><![CDATA[Developer blog by Joonas Kykkänen]]></description><link>https://www.tastyvar.com</link><generator>RSS for Node</generator><lastBuildDate>Wed, 17 Feb 2021 15:56:31 GMT</lastBuildDate><author><![CDATA[@joonaskykkanen]]></author><item><title><![CDATA[Scalable content modeling for Contentful and Gatsby]]></title><description><![CDATA[Build a block-based content model for a website built with Gatsby and Contentful]]></description><link>https://www.tastyvar.com/gatsby-contentful-content-modeling</link><guid isPermaLink="false">https://www.tastyvar.com/gatsby-contentful-content-modeling</guid><dc:creator><![CDATA[@joonaskykkanen]]></dc:creator><pubDate>Tue, 23 Feb 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This guide will tell you how to create a scalable and flexible content model with &lt;a href=&quot;https://www.contentful.com/&quot;&gt;Contentful&lt;/a&gt; and &lt;a href=&quot;https://www.gatsbyjs.com/&quot;&gt;Gatsby&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Contentful provides a flexible and loved headless content backend for many Gatsby applications. Getting started with Gatsby and Contentful is easy with the official &lt;a href=&quot;https://www.gatsbyjs.com/plugins/gatsby-source-contentful/&quot;&gt;&lt;code&gt;gatsby-source-contentful&lt;/code&gt;&lt;/a&gt; plugin, and editing content types and adding new content is a breeze using Contentful&amp;#x27;s web app.&lt;/p&gt;&lt;p&gt;However, it&amp;#x27;s also easy for teams to paint themselves into a corner when they introduce new content types without thinking about the overall architecture of their content model.&lt;/p&gt;&lt;p&gt;The purpose of this guide is to describe a technique to model your content in a way that&amp;#x27;s scalable and allows content editors to not only add new blog posts but also build new landing pages without the help of developers. The architectural pattern that is presented here could be described as a block-based design or block-based architecture.&lt;/p&gt;&lt;p&gt;Sidenote: I originally learned about blocks from a presentation given by one of Contentful&amp;#x27;s own developers. Back then I had finished my first Contentful project where we had applied a somewhat block-based architecture but lacked a proper term to describe what we were trying to achieve.&lt;/p&gt;&lt;h2&gt;Table of contents&lt;/h2&gt;&lt;ul class=&quot;list-none&quot;&gt;&lt;/ul&gt;&lt;h2 id=&quot;the-five-categories-of-content-types&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#the-five-categories-of-content-types&quot; aria-label=&quot;the five categories of content types permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The five categories of content types&lt;/h2&gt;&lt;p&gt;Your content types are all going to fall into the following five categories:&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;#application-content-types&quot;&gt;&lt;strong&gt;Applications&lt;/strong&gt;&lt;/a&gt;&lt;br/&gt;
Instead of multiple application content types, you are likely to have just one application content type and one entry where you hold all the general editable config of your site. Multi-site setups have multiple entries of the application content type but not multiple application content types (unless needed).&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;#layout-content-types&quot;&gt;&lt;strong&gt;Layouts&lt;/strong&gt;&lt;/a&gt;&lt;br/&gt;
The layout content type allows content editors to define the repeating elements of different pages (such as the navbar and footer). This is an optional layer of design since while many sites have multiple page types, they often have just one layout which means the repeating elements can be &amp;quot;hard coded&amp;quot; inside Gatsby.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;#page-content-types&quot;&gt;&lt;strong&gt;Pages&lt;/strong&gt;&lt;/a&gt;&lt;br/&gt;
A page content type is almost like a page template. Each page type gets its own content type.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;#block-content-types&quot;&gt;&lt;strong&gt;Blocks&lt;/strong&gt;&lt;/a&gt;&lt;br/&gt;
Blocks are the different headers, sections and other UI elements that content editors can add on layouts and pages.&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;#value-content-types&quot;&gt;&lt;strong&gt;Values&lt;/strong&gt;&lt;/a&gt;&lt;br/&gt;
Value content types take a group of fields and extract them into new concepts that can be reused across the site. A typical value content type is an author with name and image that blog post entries can link to.&lt;/p&gt;&lt;p&gt;In the next chapters I&amp;#x27;ll show you what these different content types look like in practice. Instead of using Contentful&amp;#x27;s web UI to create the content types, I will use Contentful&amp;#x27;s migration scripts that can be run using the &lt;a href=&quot;https://github.com/contentful/contentful-cli&quot;&gt;Contentful CLI&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;You can apply the same changes using Contentful&amp;#x27;s web app but the migration files make it easier to describe the different types and fields in written form. In addition, if you aren&amp;#x27;t using migrations yet, I would really recommend you to look into them; once you learn how to write Contenful migration scripts, you will keep using them especially when you need to refactor something in your current content model.&lt;/p&gt;&lt;p&gt;You can find all the migration scripts presented in this guide in their full form &lt;a href=&quot;https://github.com/jonaskay/contentful-content-model-example&quot;&gt;here&lt;/a&gt;. To read more about how to do migration scripting, see this &lt;a href=&quot;https://www.contentful.com/developers/docs/tutorials/cli/scripting-migrations/&quot;&gt;tutorial&lt;/a&gt;.&lt;/p&gt;&lt;h2 id=&quot;application-content-types&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#application-content-types&quot; aria-label=&quot;application content types permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Application content types&lt;/h2&gt;&lt;p&gt;There&amp;#x27;s usually some general site config that you want content editors to be able to change by themselves. For example, we might want content editors to be able to change the base meta title of our meta title template or the site logo. In order to achieve this, we need to have some place where we can store this information that doesn&amp;#x27;t belong to any specific site.&lt;/p&gt;&lt;p&gt;To add a new content type to our content model, we create a new migration file. Contentful&amp;#x27;s migration files all export a function that takes a migration object as its parameter:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;module.exports = function (migration) {};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We create a content type for the application with id set to &lt;code&gt;application&lt;/code&gt;, name set to &lt;code&gt;Application&lt;/code&gt;, and display field (or entry title field) set to &lt;code&gt;name&lt;/code&gt; field.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;module.exports = function (migration) {
  const application = migration
    .createContentType(&amp;quot;application&amp;quot;)
    .name(&amp;quot;Application&amp;quot;)
    .displayField(&amp;quot;name&amp;quot;);
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;#x27;s add the name field that we are using as the display field:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;module.exports = function (migration) {
  // ...

  const name = application.createField(&amp;quot;name&amp;quot;);
  name.name(&amp;quot;Name&amp;quot;).type(&amp;quot;Symbol&amp;quot;).required(true);
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The other fields in your application content type will be very specific to the needs of your site&amp;#x27;s setup. But let&amp;#x27;s add that base meta title as an example to our application content type:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;module.exports = function (migration) {
  // ...

  const baseMetaTitle = application.createField(&amp;quot;baseMetaTitle&amp;quot;);
  baseMetaTitle.name(&amp;quot;Base Meta Title&amp;quot;).type(&amp;quot;Symbol&amp;quot;).required(true);
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After creating the application content type, create a new entry for it and add the initial config by filling out the content fields.&lt;/p&gt;&lt;h2 id=&quot;block-content-types&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#block-content-types&quot; aria-label=&quot;block content types permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Block content types&lt;/h2&gt;&lt;p&gt;Our layout and page content types require us to have some block content types ready. Because of this, I&amp;#x27;m showing you how to create the block content types at this point—before creating any layout or page content types.&lt;/p&gt;&lt;p&gt;When building a site, you are probably not able to add all the block content types needed prior to adding any layout and page content types. This is okay; you can keep adding new blocks and updating block fields in your layout and page content types accordingly.&lt;/p&gt;&lt;h3 id=&quot;generic-and-specific-blocks&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#generic-and-specific-blocks&quot; aria-label=&quot;generic and specific blocks permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Generic and specific blocks&lt;/h3&gt;&lt;p&gt;If you create a separate block content type for each one of your page elements, you might run into your tier&amp;#x27;s content type limits. In addition to this, having tons of different block content types can make it difficult for content editors to remember what each content type does and teach others in their team how to manage the content.&lt;/p&gt;&lt;p&gt;One of the most effective ways of keeping the number of block content types under control is to identify which UI elements get constantly reused and refurbished with new content and which elements might get reused but don&amp;#x27;t necessarily see any changes in their content or other config.&lt;/p&gt;&lt;p&gt;An example of a UI element that get reused and refurbished often is a hero block that contains a heading and a background image. Each landing page usually has one but the text and image inside the heroes changes based on the purpose of the landing page.&lt;/p&gt;&lt;p&gt;An example of a UI element that might get added on every page, but doesn&amp;#x27;t change much in terms of its content, is a footer block. Footers aren&amp;#x27;t usually modified to fit specific pages but instead contain the same privacy policy links, copyright notices, and other info from one page to another.&lt;/p&gt;&lt;p&gt;What we want is to have a specific content type for hero blocks but a generic content type for footer blocks. Let&amp;#x27;s start with the migration script for our example hero block.&lt;/p&gt;&lt;h3 id=&quot;adding-specific-block-content-types&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#adding-specific-block-content-types&quot; aria-label=&quot;adding specific block content types permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Adding specific block content types&lt;/h3&gt;&lt;p&gt;As an example of the specific, non-generic block content type, let&amp;#x27;s add a hero block to our content model.&lt;/p&gt;&lt;p&gt;We start by creating a new content type &lt;code&gt;heroBlock&lt;/code&gt; with a &lt;code&gt;name&lt;/code&gt; field:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;module.exports = function (migration) {
  const heroBlock = migration
    .createContentType(&amp;quot;heroBlock&amp;quot;)
    .name(&amp;quot;Hero Block&amp;quot;)
    .displayField(&amp;quot;name&amp;quot;);

  const name = heroBlock.createField(&amp;quot;name&amp;quot;);
  name.name(&amp;quot;Name&amp;quot;).type(&amp;quot;Symbol&amp;quot;).required(true);
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Our application content type had also a name field that was used as the content&amp;#x27;s entry title. I have found it to be a good practice to give every content type a name field that allows content editors to name different blocks or pages in a way that makes it easy for them to find specific entries from a long list of content.&lt;/p&gt;&lt;p&gt;To complete our hero block, let&amp;#x27;s add fields for the hero heading and image:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;module.exports = function (migration) {
  // ...

  const heading = heroBlock.createField(&amp;quot;heading&amp;quot;);
  heading.name(&amp;quot;Heading&amp;quot;).type(&amp;quot;Symbol&amp;quot;).required(true);

  const image = heroBlock.createField(&amp;quot;image&amp;quot;);
  image
    .name(&amp;quot;Image&amp;quot;)
    .type(&amp;quot;Link&amp;quot;)
    .linkType(&amp;quot;Asset&amp;quot;)
    .validations([{ linkMimetypeGroup: [&amp;quot;image&amp;quot;] }]);
};
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;adding-the-generic-block-content-type&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#adding-the-generic-block-content-type&quot; aria-label=&quot;adding the generic block content type permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Adding the generic block content type&lt;/h3&gt;&lt;p&gt;As mentioned earlier, in order to keep our content model more manageable for content editors, we don&amp;#x27;t necessarily want to create a new block content type for each UI element.&lt;/p&gt;&lt;p&gt;The generic block content type is a content type that can be used to add individual entries for different types of UI elements. Each entry of the generic content type is mapped to a specific React component.&lt;/p&gt;&lt;p&gt;Because generic block entries are mapped to React components, these block entries are added by developers and not content editors. In other words, a content editor can&amp;#x27;t add a new block entry for a generic block content type without the help of a developer.&lt;/p&gt;&lt;p&gt;However, as mentioned before, it&amp;#x27;s not likely that content editors need to add for example a second footer or a second navbar to the site. It&amp;#x27;s often enough that the blocks for the footer and navbar exist as entries and that the content editors can link to them inside different layouts and pages.&lt;/p&gt;&lt;p&gt;It&amp;#x27;s still possible for content editors to add a second footer using the generic block content type without developers if they really need to. It&amp;#x27;s just not going to be as user-friendly as it is to add a second hero block.&lt;/p&gt;&lt;p&gt;Let&amp;#x27;s start building a new migration script for the generic block content type by creating the content type and adding a name field to it:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;module.exports = function (migration) {
  const block = migration
    .createContentType(&amp;quot;block&amp;quot;)
    .name(&amp;quot;Block&amp;quot;)
    .displayField(&amp;quot;name&amp;quot;);

  const name = block.createField(&amp;quot;name&amp;quot;);
  name.name(&amp;quot;Name&amp;quot;).type(&amp;quot;Symbol&amp;quot;).required(true);
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We add a field for the React component and include some validation logic to check that the field value is a valid React component name:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;module.exports = function (migration) {
  // ...

  const component = block.createField(&amp;quot;component&amp;quot;);
  component
    .name(&amp;quot;Component&amp;quot;)
    .type(&amp;quot;Symbol&amp;quot;)
    .required(true)
    .validations([{ regexp: { pattern: &amp;quot;^[A-Z][a-zA-Z0-9]*$&amp;quot; } }]);
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Finally, we add a field for the component properties. These properties are saved as a JSON object.&lt;/p&gt;&lt;pre&gt;&lt;code&gt;module.exports = function (migration) {
  // ...

  const properties = block.createField(&amp;quot;properties&amp;quot;);
  properties.name(&amp;quot;Properties&amp;quot;).type(&amp;quot;Object&amp;quot;);
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Component properties allow content editors to change things like marketing copy or color theme. Editing JSON is not going to be as easy as editing content fields but the assumption is that editors rarely need to do this type of editing.&lt;/p&gt;&lt;p&gt;Deciding which blocks get their own content type and which don&amp;#x27;t seems to be a balancing act between different concerns. Remember that having a content type for each one of your UI elements can make your content model more confusing to use or cause issues with the content type limits of your current Contenful plan.&lt;/p&gt;&lt;h2 id=&quot;layout-content-types&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#layout-content-types&quot; aria-label=&quot;layout content types permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Layout content types&lt;/h2&gt;&lt;p&gt;The layout content type is going to enable you to extract some of the repeating page elements into their own entries. This is an optional layer of the content model design and it might be enough for you to simply hardcode navbars and footers to your pages inside Gatsby.&lt;/p&gt;&lt;p&gt;If you do want to add layouts to your content model, you need only one content type. Let&amp;#x27;s create this content type by creating a &lt;code&gt;layout&lt;/code&gt; content type with a name field:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;module.exports = function (migration) {
  const layout = migration
    .createContentType(&amp;quot;layout&amp;quot;)
    .name(&amp;quot;Layout&amp;quot;)
    .displayField(&amp;quot;name&amp;quot;);

  const name = layout.createField(&amp;quot;name&amp;quot;);
  name.name(&amp;quot;Name&amp;quot;).type(&amp;quot;Symbol&amp;quot;).required(true);
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Layouts contain the blocks that are repeated from page to page. These blocks are placed before or after the actual content of a given page. We add separate fields for these before and after areas:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;module.exports = function (migration) {
  // ...

  const before = layout.createField(&amp;quot;before&amp;quot;);
  before
    .name(&amp;quot;Blocks added before page content&amp;quot;)
    .type(&amp;quot;Array&amp;quot;)
    .items({
      type: &amp;quot;Link&amp;quot;,
      linkType: &amp;quot;Entry&amp;quot;,
      validations: [{ linkContentType: [&amp;quot;heroBlock&amp;quot;, &amp;quot;block&amp;quot;] }],
    });

  const after = layout.createField(&amp;quot;after&amp;quot;);
  after
    .name(&amp;quot;Blocks added after page content&amp;quot;)
    .type(&amp;quot;Array&amp;quot;)
    .items({
      type: &amp;quot;Link&amp;quot;,
      linkType: &amp;quot;Entry&amp;quot;,
      validations: [{ linkContentType: [&amp;quot;heroBlock&amp;quot;, &amp;quot;block&amp;quot;] }],
    });
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Notice that we have to list the IDs of our block content types when defining the validation logic for both the &lt;code&gt;before&lt;/code&gt; and &lt;code&gt;after&lt;/code&gt; fields. By validating the content type of each linked item we can make sure that content editors are not able to add any other content besides blocks to these fields.&lt;/p&gt;&lt;p&gt;These lists of valid block IDs can be updated every time you add a new block content type.&lt;/p&gt;&lt;h2 id=&quot;page-content-types&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#page-content-types&quot; aria-label=&quot;page content types permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Page content types&lt;/h2&gt;&lt;p&gt;Each page content type could be thought of as a separate page template. In this guide, we will create one page content type for articles and one default page content type that can be used for things like the homepage and the about page.&lt;/p&gt;&lt;h3 id=&quot;adding-a-default-page-content-type&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#adding-a-default-page-content-type&quot; aria-label=&quot;adding a default page content type permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Adding a default page content type&lt;/h3&gt;&lt;p&gt;Let&amp;#x27;s start with the default page content type. We create a migration that creates the &lt;code&gt;page&lt;/code&gt; content type and adds the name field to it:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;module.exports = function (migration) {
  const page = migration
    .createContentType(&amp;quot;page&amp;quot;)
    .name(&amp;quot;Page&amp;quot;)
    .displayField(&amp;quot;name&amp;quot;);

  const name = page.createField(&amp;quot;name&amp;quot;);
  name.name(&amp;quot;Name&amp;quot;).type(&amp;quot;Symbol&amp;quot;).required(true);
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Next, we create a field for the page slug (slug is a part of the URL path that is used to identify different pages from each other). We also change the appearance of this field to a slug field control:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;module.exports = function (migration) {
  // ...

  const slug = page.createField(&amp;quot;slug&amp;quot;);
  slug
    .name(&amp;quot;Slug&amp;quot;)
    .type(&amp;quot;Symbol&amp;quot;)
    .required(true)
    .validations([{ unique: true }]);
  page.changeFieldControl(&amp;quot;slug&amp;quot;, &amp;quot;builtin&amp;quot;, &amp;quot;slugEditor&amp;quot;);
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We add a &lt;code&gt;layout&lt;/code&gt; field to link each page to a specific layout:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;module.exports = function (migration) {
  // ...

  const layout = page.createField(&amp;quot;layout&amp;quot;);
  layout
    .name(&amp;quot;Layout&amp;quot;)
    .type(&amp;quot;Link&amp;quot;)
    .linkType(&amp;quot;Entry&amp;quot;)
    .required(true)
    .validations([{ linkContentType: [&amp;quot;layout&amp;quot;] }]);
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Finally, we add a field for the actual content of the page which in our case consists of different blocks. As is the case with the layout content type, we have to make sure that content editors can only add blocks (and not other content) to the field by listing all the IDs of the available block content types inside our item validations:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;module.exports = function (migration) {
  // ...

  const blocks = page.createField(&amp;quot;blocks&amp;quot;);
  blocks
    .name(&amp;quot;Blocks&amp;quot;)
    .type(&amp;quot;Array&amp;quot;)
    .items({
      type: &amp;quot;Link&amp;quot;,
      linkType: &amp;quot;Entry&amp;quot;,
      validations: [{ linkContentType: [&amp;quot;heroBlock&amp;quot;, &amp;quot;block&amp;quot;] }],
    });
};
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;adding-an-article-page-content-type&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#adding-an-article-page-content-type&quot; aria-label=&quot;adding an article page content type permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Adding an article page content type&lt;/h3&gt;&lt;p&gt;We can add another page content type for article pages to demonstrate how we might structure other, more specific page content types.&lt;/p&gt;&lt;p&gt;We first create the content type itself and add the same &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;slug&lt;/code&gt;, and &lt;code&gt;layout&lt;/code&gt; fields that we added to the default page content type:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;module.exports = function (migration) {
  const articlePage = migration
    .createContentType(&amp;quot;articlePage&amp;quot;)
    .name(&amp;quot;Article Page&amp;quot;)
    .displayField(&amp;quot;name&amp;quot;);

  const name = articlePage.createField(&amp;quot;name&amp;quot;);
  name.name(&amp;quot;Name&amp;quot;).type(&amp;quot;Symbol&amp;quot;).required(true);

  const slug = articlePage.createField(&amp;quot;slug&amp;quot;);
  slug
    .name(&amp;quot;Slug&amp;quot;)
    .type(&amp;quot;Symbol&amp;quot;)
    .required(true)
    .validations([{ unique: true }]);
  articlePage.changeFieldControl(&amp;quot;slug&amp;quot;, &amp;quot;builtin&amp;quot;, &amp;quot;slugEditor&amp;quot;);

  const layout = articlePage.createField(&amp;quot;layout&amp;quot;);
  layout
    .name(&amp;quot;Layout&amp;quot;)
    .type(&amp;quot;Link&amp;quot;)
    .linkType(&amp;quot;Entry&amp;quot;)
    .required(true)
    .validations([{ linkContentType: [&amp;quot;layout&amp;quot;] }]);
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Instead of the &lt;code&gt;blocks&lt;/code&gt; field, we want to add a rich text field for the article content. This field will make it easier for content editors to create article pages that consist mainly of written text with different heading levels and paragraphs.&lt;/p&gt;&lt;p&gt;We can call this field &lt;code&gt;body&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;module.exports = function (migration) {
  // ...

  const body = articlePage.createField(&amp;quot;body&amp;quot;);
  body.name(&amp;quot;Body&amp;quot;).type(&amp;quot;RichText&amp;quot;);
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We still want to give editors the option to add blocks to specific article pages if they want to. To achieve this, we add &lt;code&gt;before&lt;/code&gt; and &lt;code&gt;after&lt;/code&gt; fields to the article page content type. Both of these fields are used to link specific blocks to the page. The &lt;code&gt;before&lt;/code&gt; field is reserved for blocks that should be displayed before the body text and the &lt;code&gt;after&lt;/code&gt; field for blocks that should be displayed after the body text:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;module.exports = function (migration) {
  // ...

  const before = articlePage.createField(&amp;quot;before&amp;quot;);
  before
    .name(&amp;quot;Blocks added before body text&amp;quot;)
    .type(&amp;quot;Array&amp;quot;)
    .items({
      type: &amp;quot;Link&amp;quot;,
      linkType: &amp;quot;Entry&amp;quot;,
      validations: [{ linkContentType: [&amp;quot;heroBlock&amp;quot;, &amp;quot;block&amp;quot;] }],
    });

  const after = articlePage.createField(&amp;quot;after&amp;quot;);
  after
    .name(&amp;quot;Blocks added after body text&amp;quot;)
    .type(&amp;quot;Array&amp;quot;)
    .items({
      type: &amp;quot;Link&amp;quot;,
      linkType: &amp;quot;Entry&amp;quot;,
      validations: [{ linkContentType: [&amp;quot;heroBlock&amp;quot;, &amp;quot;block&amp;quot;] }],
    });
};
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;value-content-types&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#value-content-types&quot; aria-label=&quot;value content types permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Value content types&lt;/h2&gt;&lt;p&gt;You may want to abstract some of the content into their own content types so that you can reuse them inside other content entries. For example, if we want to add authors to our article pages, we should create a new author content type so that author content entries can be linked to multiple different articles.&lt;/p&gt;&lt;p&gt;These value content types are not blocks because they don&amp;#x27;t really correspond to an UI element that content editors can add on their pages between other blocks. Value content types simply take some set of fields and combine them to form a new concept to our content model.&lt;/p&gt;&lt;p&gt;As an example, let&amp;#x27;s add authors to our article pages.&lt;/p&gt;&lt;p&gt;First, we create the author content type with author name and avatar:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;module.exports = function (migration) {
  const author = migration
    .createContentType(&amp;quot;author&amp;quot;)
    .name(&amp;quot;Author&amp;quot;)
    .displayField(&amp;quot;name&amp;quot;);

  const name = author.createField(&amp;quot;name&amp;quot;);
  name.name(&amp;quot;Name&amp;quot;).type(&amp;quot;Symbol&amp;quot;).required(true);

  const avatar = author.createField(&amp;quot;avatar&amp;quot;);
  avatar
    .name(&amp;quot;Avatar&amp;quot;)
    .type(&amp;quot;Link&amp;quot;)
    .linkType(&amp;quot;Asset&amp;quot;)
    .validations([{ linkMimetypeGroup: [&amp;quot;image&amp;quot;] }]);
};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Next, we need to add a field to our article page that links to author entries. Let&amp;#x27;s edit the existing article page content type inside the current migration script:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;module.exports = function (migration) {
  // ...

  const articlePage = migration.editContentType(&amp;quot;articlePage&amp;quot;);
  const authorField = articlePage.createField(&amp;quot;author&amp;quot;);
  authorField
    .name(&amp;quot;Author&amp;quot;)
    .type(&amp;quot;Link&amp;quot;)
    .linkType(&amp;quot;Entry&amp;quot;)
    .validations([{ linkContentType: [&amp;quot;author&amp;quot;] }]);
};
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;conclusion&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#conclusion&quot; aria-label=&quot;conclusion permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;&lt;p&gt;This guide laid out a way for you to build a scalable and flexible content model for a site built with Gatsby and Contentful. I hope you found it helpful. If so, you can also help others by sharing this guide on Twitter or wherever else.&lt;/p&gt;&lt;p&gt;You can find all the migration files presented in this guide &lt;a href=&quot;https://github.com/jonaskay/contentful-content-model-example&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title><![CDATA[Deploying a Ruby on Rails 6 application on Google App Engine Standard with Travis CI]]></title><description><![CDATA[Step-by-step guide for deploying a Ruby on Rails 6 application with a PostgreSQL database on Google App Engine standard environment using Travis CI]]></description><link>https://www.tastyvar.com/rails-appengine</link><guid isPermaLink="false">https://www.tastyvar.com/rails-appengine</guid><dc:creator><![CDATA[@joonaskykkanen]]></dc:creator><pubDate>Tue, 27 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This guide will walk you through how to deploy a Ruby on Rails 6 application with a PostgreSQL database on Google App Engine standard environment using a Travis CI pipeline.&lt;/p&gt;&lt;h2&gt;Table of contents&lt;/h2&gt;&lt;ul class=&quot;list-none&quot;&gt;&lt;/ul&gt;&lt;h2 id=&quot;app-engine-standard-vs-flexible-environment&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#app-engine-standard-vs-flexible-environment&quot; aria-label=&quot;app engine standard vs flexible environment permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;App Engine standard vs. flexible environment&lt;/h2&gt;&lt;p&gt;Google added Ruby support for App Engine standard environment in August 2019. Before this, Rails developers could only deploy their apps on App Engine flexible environment.&lt;/p&gt;&lt;p&gt;If you are currently running a Rails application using the flexible environment, you might want to consider switching to the standard environment if you want to &lt;strong&gt;bring down your deployment times&lt;/strong&gt; (deployment times are around 4-7 minutes on the flexible environment and 1-3 minutes on the standard environment) or use &lt;strong&gt;scale-to-zero&lt;/strong&gt; (instances can be scaled down when no one is using them).&lt;/p&gt;&lt;p&gt;As of September 2020, App Engine standard environment supports only Ruby 2.5. If downgrading or upgrading to 2.5 is not possible for you, you are not able to deploy your app on App Engine standard environment.&lt;/p&gt;&lt;p&gt;If you are starting a new Rails application or moving your application for example from Heroku to App Engine, it&amp;#x27;s likely that the standard environment will be your pick. However, there are lots of other technical differences between the two environments that you might need to take into consideration in the context of your project. For a more in-depth comparison of App Engine standard vs. flexible environment, see GCP&amp;#x27;s article &lt;a href=&quot;https://cloud.google.com/appengine/docs/the-appengine-environments&quot;&gt;Choosing an App Engine environment&lt;/a&gt;.&lt;/p&gt;&lt;h2 id=&quot;prerequisites&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#prerequisites&quot; aria-label=&quot;prerequisites permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Prerequisites&lt;/h2&gt;&lt;p&gt;This guide assumes that you have a working Rails project installed on your computer and that you have a user account in the following services:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;https://cloud.google.com/gcp/&quot;&gt;Google Cloud Platform&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://travis-ci.com/&quot;&gt;Travis CI&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://github.com/&quot;&gt;GitHub&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Signing up for all of these services is free. However, as of September 2020 GCP will charge you for your PostgreSQL instance. &lt;strong&gt;It&amp;#x27;s important to clean up your GCP resources after finishing this guide if you don&amp;#x27;t want these costs to continue running.&lt;/strong&gt; This guide has separate instructions on how to do this and you can find them in chapter &lt;a href=&quot;#clean-up&quot;&gt;Clean-up&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;If you don&amp;#x27;t have Rails installed or a working Rails application on your computer, GoRails has excellent guides on how to install and start a new Rails application on Ubuntu, macOS, or Windows. You can find them &lt;a href=&quot;https://gorails.com/setup/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;h2 id=&quot;setting-up-travis-ci&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#setting-up-travis-ci&quot; aria-label=&quot;setting up travis ci permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Setting up Travis CI&lt;/h2&gt;&lt;p&gt;Before setting up a new App Engine application, we will initialize our Travis CI pipeline.&lt;/p&gt;&lt;p&gt;If you have signed in to Travis CI using your GitHub account, all you need to do to start your Travis CI builds is to add the &lt;code&gt;.travis.yml&lt;/code&gt; file to your project root and push the changes to your GitHub repository. Travis CI will start a new build automatically on each push.&lt;/p&gt;&lt;h3 id=&quot;configuring-travis-ci&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#configuring-travis-ci&quot; aria-label=&quot;configuring travis ci permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Configuring Travis CI&lt;/h3&gt;&lt;p&gt;Travis CI pipelines are configured using a YAML file that lives inside your project root directory.&lt;/p&gt;&lt;p&gt;Create a new file called &lt;code&gt;.travis.yml&lt;/code&gt; inside your project&amp;#x27;s root directory and add the following content to it (make sure to call your file &lt;code&gt;.travis.yml&lt;/code&gt; and not &lt;code&gt;.travis.yaml&lt;/code&gt;):&lt;/p&gt;&lt;pre&gt;&lt;code&gt;dist: xenial
language: ruby
cache:
  bundler: true
  yarn: true
  directories:
    - &amp;quot;node_modules&amp;quot;
services:
  - postgresql
install:
  - bundle install --jobs=3 --retry=3 --deployment
  - nvm install
  - bin/yarn
before_script:
  - bin/rails db:prepare
script:
  - bin/rails test
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;#x27;s briefly go through these different lines.&lt;/p&gt;&lt;p&gt;&lt;code&gt;dist: xenial&lt;/code&gt; and &lt;code&gt;language: ruby&lt;/code&gt; tells Travis CI to use the Ubuntu Xenial and Ruby build environments.&lt;/p&gt;&lt;p&gt;&lt;code&gt;cache&lt;/code&gt; tells Travis CI to cache our dependencies for Bundler (&lt;code&gt;bundler: true&lt;/code&gt;) and Node.js (&lt;code&gt;yarn: true&lt;/code&gt; and &lt;code&gt;&amp;quot;node_modules&amp;quot;&lt;/code&gt; under &lt;code&gt;directories&lt;/code&gt;).&lt;/p&gt;&lt;p&gt;Under &lt;code&gt;services&lt;/code&gt; we add &lt;code&gt;postgresql&lt;/code&gt; to run a PostgreSQL instance during our build. We need this database instance to run tests (and later on in the guide to precompile assets). This database instance is not our production database. It&amp;#x27;s just a temporary instance that gets thrashed every time our Travis CI build finishes.&lt;/p&gt;&lt;p&gt;During the &lt;code&gt;install&lt;/code&gt; phase we install all our dependencies: First, we install our Bundler dependencies with Travis CI&amp;#x27;s default bundler command &lt;code&gt;bundle install --jobs=3 --retry=3 --deployment&lt;/code&gt;. After that, we instruct Node Version Manager to install the correct Node.js version with &lt;code&gt;nvm install&lt;/code&gt;. Finally, we run the command &lt;code&gt;bin/yarn&lt;/code&gt; to install Node.js dependencies.&lt;/p&gt;&lt;p&gt;In our &lt;code&gt;before_script&lt;/code&gt; step we prepare our database for the tests. The command &lt;code&gt;bin/rails db:prepare&lt;/code&gt; was introduced in Rails 6 and it either runs migrations or creates the database, loads the schema, and seeds the database if no database exists.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Sidenote:&lt;/em&gt; Travis CI doesn&amp;#x27;t store our database between builds and therefore &lt;code&gt;bin/rails db:prepare&lt;/code&gt; will always create a new database and seed it. This means that we could easily replace &lt;code&gt;bin/rails db:prepare&lt;/code&gt; with &lt;code&gt;bin/rails db:setup&lt;/code&gt;. But I thought that this is a great opportunity to get more familiar with this new Rails 6 database task.&lt;/p&gt;&lt;p&gt;Finally, we run our tests to make sure everything is working during the &lt;code&gt;script&lt;/code&gt; phase with the command &lt;code&gt;bin/rails test&lt;/code&gt;.&lt;/p&gt;&lt;h3 id=&quot;specifying-ruby-and-nodejs-versions&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#specifying-ruby-and-nodejs-versions&quot; aria-label=&quot;specifying ruby and nodejs versions permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Specifying Ruby and Node.js versions&lt;/h3&gt;&lt;p&gt;We could specify the required Ruby and Node.js versions inside &lt;code&gt;.travis.yml&lt;/code&gt; (see &lt;a href=&quot;https://docs.travis-ci.com/user/languages/ruby/#specifying-ruby-versions-and-implementations&quot;&gt;Specifying Ruby versions&lt;/a&gt; and &lt;a href=&quot;https://docs.travis-ci.com/user/languages/ruby/#specifying-ruby-versions-and-implementations&quot;&gt;Specifying Node.js versions&lt;/a&gt;) but we will instead specify the required versions by adding &lt;code&gt;.ruby-version&lt;/code&gt; and &lt;code&gt;.nvmrc&lt;/code&gt; files inside our project root. Travis CI is able to pick up the correct Ruby and Node.js versions from these files.&lt;/p&gt;&lt;p&gt;Why add these config files? It&amp;#x27;s likely that other developers working on the project are using &lt;a href=&quot;https://rvm.io/&quot;&gt;Ruby Version Manager (RVM)&lt;/a&gt; (or &lt;a href=&quot;https://github.com/rbenv/rbenv&quot;&gt;rbenv&lt;/a&gt;) and &lt;a href=&quot;https://github.com/nvm-sh/nvm&quot;&gt;Node Version Manager (nvm)&lt;/a&gt; to manage their Ruby and Node.js versions. Just like Travis CI, these version managers are able to read the version files automatically and instruct your fellow developers to install and use the correct language versions while working on the project.&lt;/p&gt;&lt;p&gt;To add the &lt;code&gt;.ruby-version&lt;/code&gt; file, run&lt;/p&gt;&lt;pre&gt;&lt;code&gt;$ echo &amp;quot;ruby-2.5.8&amp;quot; &amp;gt; .ruby-version
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;Sidenote:&lt;/em&gt; Remember that as of September 2020 App Engine standard environment supports only Ruby 2.5.&lt;/p&gt;&lt;p&gt;In addition to specifying a Ruby version with a &lt;code&gt;.ruby-version&lt;/code&gt; file, we need to make sure our &lt;code&gt;Gemfile&lt;/code&gt; doesn&amp;#x27;t restrict the Ruby version to a specific version. This is because App Engine might update the patch version.&lt;/p&gt;&lt;p&gt;Inside your &lt;code&gt;Gemfile&lt;/code&gt;, define the required Ruby version as:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;ruby &amp;#x27;~&amp;gt; 2.5.0&amp;#x27;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Finally, let&amp;#x27;s define our Node.js version. To add the &lt;code&gt;.nvmrc&lt;/code&gt; file with the latest LTS version (as of September 2020), run&lt;/p&gt;&lt;pre&gt;&lt;code&gt;$ echo &amp;quot;12.18.4&amp;quot; &amp;gt; .nvmrc
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;first-travis-ci-build&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#first-travis-ci-build&quot; aria-label=&quot;first travis ci build permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;First Travis CI build&lt;/h3&gt;&lt;p&gt;Save the &lt;code&gt;.travis.yml&lt;/code&gt; file and commit your changes by running&lt;/p&gt;&lt;pre&gt;&lt;code&gt;$ git add .travis.yml
$ git commit -m &amp;quot;Add .travis.yml&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After the commit, push your changes to GitHub by running&lt;/p&gt;&lt;pre&gt;&lt;code&gt;$ git push origin master
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you go to &lt;a href=&quot;https://travis-ci.com/&quot;&gt;https://travis-ci.com/&lt;/a&gt;, you should see your new build appear. If your build errors, double check your typing and look for clues inside the build logs to figure out what&amp;#x27;s causing the build to error. If your build doesn&amp;#x27;t appear on Travis CI even after waiting for couple of minutes, make sure you have correctly named the config file &lt;code&gt;.travis.yml&lt;/code&gt;: the filename begins with a dot and its extension is &lt;code&gt;.yml&lt;/code&gt; – not &lt;code&gt;.yaml&lt;/code&gt;.&lt;/p&gt;&lt;h2 id=&quot;setting-up-app-engine&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#setting-up-app-engine&quot; aria-label=&quot;setting up app engine permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Setting up App Engine&lt;/h2&gt;&lt;h3 id=&quot;creating-a-new-app-engine-application&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#creating-a-new-app-engine-application&quot; aria-label=&quot;creating a new app engine application permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Creating a new App Engine application&lt;/h3&gt;&lt;p&gt;Before we can add a deploy step to our CI/CD pipeline, we need to initialize our application on App Engine.&lt;/p&gt;&lt;p&gt;First, log in to the Google Cloud console at &lt;a href=&quot;https://console.cloud.google.com/&quot;&gt;https://console.cloud.google.com/&lt;/a&gt;. Select or create a new project in the Cloud console. Make sure that billing is enabled for your project.&lt;/p&gt;&lt;p&gt;After that, go to &lt;code&gt;App Engine&lt;/code&gt;. Create a new application in a region that suits your needs. After selecting the region, you can optionally select the language and environment for your application. Our application&amp;#x27;s language will be &lt;code&gt;Ruby&lt;/code&gt; and environment &lt;code&gt;Standard&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Instead of using the Cloud console, you can also create the App Engine application using the Google Cloud SDK by running&lt;/p&gt;&lt;pre&gt;&lt;code&gt;$ gcloud app create
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For more information on how to install the Cloud SDK on your computer, see &lt;a href=&quot;https://cloud.google.com/sdk/docs/quickstarts&quot;&gt;https://cloud.google.com/sdk/docs/quickstarts&lt;/a&gt;.&lt;/p&gt;&lt;h3 id=&quot;configuring-the-app-engine-application&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#configuring-the-app-engine-application&quot; aria-label=&quot;configuring the app engine application permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Configuring the App Engine application&lt;/h3&gt;&lt;p&gt;App Engine applications are configured using an &lt;code&gt;app.yaml&lt;/code&gt; file which is located inside the root directory of your project.&lt;/p&gt;&lt;p&gt;Create the &lt;code&gt;app.yaml&lt;/code&gt; file and add the following to it:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;runtime: ruby25
entrypoint: bundle exec rails server -p $PORT
env_variables:
  RAILS_ENV: production
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;#x27;s go through these lines.&lt;/p&gt;&lt;p&gt;&lt;code&gt;runtime&lt;/code&gt; defines the runtime environment for your App Engine application. As of September 2020, the only available standard environment Ruby runtime is &lt;code&gt;ruby25&lt;/code&gt; (Ruby 2.5).&lt;/p&gt;&lt;p&gt;&lt;code&gt;entrypoint&lt;/code&gt; is the command that starts your application. The value for the environment variable &lt;code&gt;PORT&lt;/code&gt; is set up by App Engine on our behalf.&lt;/p&gt;&lt;p&gt;Finally, &lt;code&gt;env_variables&lt;/code&gt; allows us to define custom environment variables for our instance.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; don&amp;#x27;t add any sensitive information (such as passwords or API keys) inside your &lt;code&gt;app.yaml&lt;/code&gt; file.&lt;/p&gt;&lt;p&gt;In this guide we will commit &lt;code&gt;app.yaml&lt;/code&gt; to our git history and push it to GitHub. If your GitHub repository is public, your unencrypted passwords and API keys specified inside &lt;code&gt;app.yaml&lt;/code&gt; are available to everyone who stumbles across your repository. Even if your GitHub repository is private, committing sensitive information in your git history without any kind of encryption is not a good practice.&lt;/p&gt;&lt;p&gt;We will discuss more about managing sensitive information in chapter &lt;a href=&quot;#configuring-database-connections&quot;&gt;Configuring database connections&lt;/a&gt;.&lt;/p&gt;&lt;h3 id=&quot;choosing-which-files-to-deploy-to-app-engine&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#choosing-which-files-to-deploy-to-app-engine&quot; aria-label=&quot;choosing which files to deploy to app engine permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Choosing which files to deploy to App Engine&lt;/h3&gt;&lt;p&gt;When you deploy a new version of your application to App Engine, the gcloud command-line tool (part of the Google Cloud SDK) uploads your current project files to App Engine where a new version of your application gets installed. You can&amp;#x27;t deploy a new version directly from GitHub. Instead, you need to do your deployment from an environment that has Google Cloud SDK installed. This environment can be your computer or a virtual machine such as Travis CI.&lt;/p&gt;&lt;p&gt;If we don&amp;#x27;t upload our files directly from git history but instead from our current project directory, we will upload lots of extra files that App Engine doesn&amp;#x27;t really need. These extra files include for example our &lt;code&gt;node_modules&lt;/code&gt; and &lt;code&gt;tmp&lt;/code&gt; folders.&lt;/p&gt;&lt;p&gt;gcloud does ignore some files during App Engine deployments by default. However, this default ignore list will actually contain files that we do want to upload to App Engine. These &amp;quot;ignored but needed&amp;quot; files are our precompiled assets which we will get to in chapter &lt;a href=&quot;#preparing-assets&quot;&gt;Preparing assets&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Luckily, we can add a &lt;code&gt;.gcloudignore&lt;/code&gt; file inside the root directory of our project. This file will tell gcloud what files to skip when uploading project files to App Engine. &lt;code&gt;.gcloudignore&lt;/code&gt; will also override gcloud&amp;#x27;s default ignore list.&lt;/p&gt;&lt;p&gt;If you are familiar with the syntax of &lt;code&gt;.gitignore&lt;/code&gt;, you should have no problem adding new rules to &lt;code&gt;.gcloudignore&lt;/code&gt;: the &lt;code&gt;.gcloudignore&lt;/code&gt; syntax is heavily inspired by &lt;code&gt;.gitignore&lt;/code&gt;. To read more about &lt;code&gt;.gcloudignore&lt;/code&gt;, see &lt;a href=&quot;https://cloud.google.com/sdk/gcloud/reference/topic/gcloudignore&quot;&gt;https://cloud.google.com/sdk/gcloud/reference/topic/gcloudignore&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Here is an example &lt;code&gt;.gloudignore&lt;/code&gt; file for a new Rails 6 project:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;# Ignore itself
.gcloudignore

# Ignore git files
.git
.gitignore

# Ignore gems installed by Travis CI
/vendor/bundle

# Ignore other local Rails files
/.bundle
/log/*
/tmp/*
!/log/.keep
!/tmp/.keep
/tmp/pids/*
!/tmp/pids/
!/tmp/pids/.keep
/storage/*
!/storage/.keep
.byebug_history
/public/packs-test
/node_modules
/yarn-error.log
yarn-debug.log*
.yarn-integrity
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you have added new rules to your &lt;code&gt;.gitignore&lt;/code&gt; file, you should add them also to &lt;code&gt;.gcloudignore&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Our &lt;code&gt;.gitignore&lt;/code&gt; and &lt;code&gt;.gcloudignore&lt;/code&gt; are not one-to-one in terms of ignore rules:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;code&gt;.gcloudignore&lt;/code&gt; ignores itself (&lt;code&gt;.gitignore&lt;/code&gt;) and git history (&lt;code&gt;.git&lt;/code&gt; and &lt;code&gt;.gitignore&lt;/code&gt;).&lt;/li&gt;&lt;li&gt;&lt;code&gt;.gcloudignore&lt;/code&gt; doesn&amp;#x27;t ignore &lt;code&gt;/public/assets&lt;/code&gt; and &lt;code&gt;/public/packs&lt;/code&gt;. These directories contain our precompiled assets that we need to upload to App Engine.&lt;/li&gt;&lt;li&gt;&lt;code&gt;.gcloudignore&lt;/code&gt; doesn&amp;#x27;t ignore &lt;code&gt;/config/master.key&lt;/code&gt;. This is the key that allows us to access different application credentials on App Engine. &lt;strong&gt;Important:&lt;/strong&gt; never commit this file to your git history (keep it in &lt;code&gt;.gitignore&lt;/code&gt;)!&lt;/li&gt;&lt;li&gt;Travis CI will install gems in &lt;code&gt;/vendor/bundle&lt;/code&gt; inside our project. If we don&amp;#x27;t ignore this directory, we will end up uploading extra copies of our gems to App Engine.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;If at any point gcloud errors because your deployment has too many files, you should double check your &lt;code&gt;.gcloudignore&lt;/code&gt; file. When debugging this error, you might find it also helpful to view the current deployed source code by navigating to &lt;code&gt;App Engine &amp;gt; Dashboard &amp;gt; Debug&lt;/code&gt; in Cloud console and seeing if your source contains any files that shouldn&amp;#x27;t be there.&lt;/p&gt;&lt;h2 id=&quot;adding-gcloud-to-travis-ci&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#adding-gcloud-to-travis-ci&quot; aria-label=&quot;adding gcloud to travis ci permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Adding gcloud to Travis CI&lt;/h2&gt;&lt;h3 id=&quot;installing-google-cloud-sdk&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#installing-google-cloud-sdk&quot; aria-label=&quot;installing google cloud sdk permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Installing Google Cloud SDK&lt;/h3&gt;&lt;p&gt;As mentioned before, we need the gcloud command-line tool to deploy our application to App Engine. The gcloud command-line tool is part of the Google Cloud SDK.&lt;/p&gt;&lt;p&gt;Since we want to deploy the application using our CI/CD pipeline, we need to install the Google Cloud SDK for our Travis CI virtual machine. We will also cache this install to make our builds faster.&lt;/p&gt;&lt;p&gt;First, add a new cached directory for Google Cloud SDK inside &lt;code&gt;.travis.yml&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;cache:
    bundler: true
    yarn: true
    directories:
        - &amp;quot;node_modules&amp;quot;
        - &amp;quot;$HOME/google-cloud-sdk/&amp;quot;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After that, add a new &lt;code&gt;before_install&lt;/code&gt; step before the &lt;code&gt;install&lt;/code&gt; step inside &lt;code&gt;.travis.yml&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;before_install:
    - if [ ! -d ${HOME}/google-cloud-sdk ]; then
            curl https://sdk.cloud.google.com | bash;
        fi
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This command checks if an existing Google Cloud SDK installation exists in our virtual machine and installs it if needed.&lt;/p&gt;&lt;h3 id=&quot;authorizing-gcloud-to-access-google-cloud-platform&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#authorizing-gcloud-to-access-google-cloud-platform&quot; aria-label=&quot;authorizing gcloud to access google cloud platform permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Authorizing gcloud to access Google Cloud Platform&lt;/h3&gt;&lt;p&gt;Our Travis CI pipeline now has gcloud installed but it&amp;#x27;s not authorized to access our Google Cloud project. In order to authorize Travis CI to run our App Engine deployments, we need to add a service account for it.&lt;/p&gt;&lt;p&gt;Inside Cloud console, go to &lt;code&gt;IAM &amp;amp; Admin &amp;gt; Service Accounts&lt;/code&gt; and select &lt;code&gt;Create service account&lt;/code&gt;. You can give whatever name and ID you want for the service account (e.g. I have named my service account &amp;quot;Travis CI&amp;quot; and given it the ID &amp;quot;travis-ci&amp;quot;). After that, add the roles &lt;code&gt;App Engine Admin&lt;/code&gt;, &lt;code&gt;Cloud Build Service Account&lt;/code&gt;, and &lt;code&gt;Storage Admin&lt;/code&gt; to the service account.&lt;/p&gt;&lt;p&gt;Create new credentials for the service account by selecting the account from the list of service accounts and then selecting &lt;code&gt;Add key &amp;gt; Create new key&lt;/code&gt; under the Keys section. When asked for the key type, choose JSON.&lt;/p&gt;&lt;p&gt;The credentials are automatically downloaded on your computer. Rename the downloaded file as &lt;code&gt;service-account.json&lt;/code&gt; and move it to your project directory.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Do not commit this file to your git history. To be extra sure you won&amp;#x27;t accidentally do this now or in the future, add the following line to your &lt;code&gt;.gitignore&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;service-account.json
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Feel free to also delete &lt;code&gt;service-account.json&lt;/code&gt; from your computer after we have finished encrypting it (don&amp;#x27;t delete it yet).&lt;/p&gt;&lt;p&gt;In order to safely upload these credentials to GitHub and Travis CI, we need to encrypt them using the Travis CI Command Line Client.&lt;/p&gt;&lt;p&gt;You can install the client by running:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;$ gem install travis
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After installing the client, authorize it with Travis CI by running:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;$ travis login --com
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To read more about the Travis Client, see &lt;a href=&quot;https://github.com/travis-ci/travis.rb&quot;&gt;https://github.com/travis-ci/travis.rb&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Encrypt the credentials file by running:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;$ travis encrypt-file service-account.json --com
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;em&gt;Sidenote:&lt;/em&gt; the &lt;code&gt;--com&lt;/code&gt; option tells the client to use travis-ci.com and not travis-ci.org. All new users and projects should only use travis-ci.com.&lt;/p&gt;&lt;p&gt;The output of the &lt;code&gt;encrypt-file&lt;/code&gt; command will contain instructions on how to also decrypt &lt;code&gt;service-account.json&lt;/code&gt;. Copy the decrypt command from the instructions and add it as the first command in the &lt;code&gt;before_install&lt;/code&gt; step inside &lt;code&gt;.travis.yml&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;openssl aes-256-cbc -K $encrypted_ENCRYPTION_ID_key -iv $encrypted_ENCRYPTION_ID_iv -in service-account.json.enc -out service-account.json -d
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Finally, we can authorize gcloud by adding the following line as the last &lt;code&gt;before_install&lt;/code&gt; step command:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;gcloud auth activate-service-account --key-file service-account.json
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here is what our final &lt;code&gt;before_install&lt;/code&gt; step should look like:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;before_install:
    - openssl aes-256-cbc -K $encrypted_9f3b5599b056_key -iv $encrypted_9f3b5599b056_iv -in service-account.json.enc -out service-account.json -d
    - if [ ! -d ${HOME}/google-cloud-sdk ]; then
            curl https://sdk.cloud.google.com | bash;
        fi
    - gcloud auth activate-service-account --key-file service-account.json
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;enabling-app-engine-apis&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#enabling-app-engine-apis&quot; aria-label=&quot;enabling app engine apis permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Enabling App Engine APIs&lt;/h3&gt;&lt;p&gt;In addition to creating and authorizing a service account to deploy our App Engine application, we need to enable few APIs using the Cloud console.&lt;/p&gt;&lt;p&gt;Start by navigating to &lt;code&gt;APIs &amp;amp; Services &amp;gt; Library&lt;/code&gt; in the cloud console.&lt;/p&gt;&lt;p&gt;The APIs that we need to enable are:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;App Engine Admin API&lt;/li&gt;&lt;li&gt;Cloud Build API&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Search for each of these APIs on the library page and enable them for your project.&lt;/p&gt;&lt;h3 id=&quot;connecting-cloud-sdk-to-the-cloud-project&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#connecting-cloud-sdk-to-the-cloud-project&quot; aria-label=&quot;connecting cloud sdk to the cloud project permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Connecting Cloud SDK to the Cloud project&lt;/h3&gt;&lt;p&gt;Each App Engine application lives inside a Cloud project. We need to tell gcloud which Cloud project we are using to run our application in.&lt;/p&gt;&lt;p&gt;Add the following command to the &lt;code&gt;install&lt;/code&gt; step in &lt;code&gt;.travis.yml&lt;/code&gt; (replace &lt;code&gt;MY_PROJECT&lt;/code&gt; with your project ID):&lt;/p&gt;&lt;pre&gt;&lt;code&gt;gcloud config set project MY_PROJECT
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here is what our final &lt;code&gt;install&lt;/code&gt; step should look like:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;install:
    - gcloud config set project MY_PROJECT
    - bundle install --jobs=3 --retry=3 --deployment
    - nvm install
    - bin/yarn
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;deploying-the-app-to-app-engine&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#deploying-the-app-to-app-engine&quot; aria-label=&quot;deploying the app to app engine permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Deploying the app to App Engine&lt;/h2&gt;&lt;p&gt;Before we can deploy the first version of our app to App Engine, we need to share our &lt;code&gt;master.key&lt;/code&gt; securely with App Engine and precompile our assets. After that, we are ready to deploy our app.&lt;/p&gt;&lt;h3 id=&quot;managing-secrets&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#managing-secrets&quot; aria-label=&quot;managing secrets permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Managing secrets&lt;/h3&gt;&lt;p&gt;If you have previously used services like Heroku to deploy your Rails applications, you have probably managed sensitive information such as passwords and API keys using environment variables and setting them using an admin panel.&lt;/p&gt;&lt;p&gt;App Engine doesn&amp;#x27;t provide us with such a GUI for setting environment variables. Instead, you define all your environment variables inside &lt;code&gt;app.yaml&lt;/code&gt; which in our case gets committed to our git history and shared to GitHub (possibly for the whole world to see if our repo is public).&lt;/p&gt;&lt;p&gt;Fortunately, Rails comes with the credentials feature that allows us to encrypt our secrets for safe sharing. Secrets are stored inside &lt;code&gt;config/credentials.yml.enc&lt;/code&gt; and encrypted/decrypted using &lt;code&gt;config/master.key&lt;/code&gt; (which is ignored by git by default). To read more about credentials, see &lt;a href=&quot;https://edgeguides.rubyonrails.org/security.html#custom-credentials&quot;&gt;the official documentation&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;&lt;code&gt;config/master.key&lt;/code&gt; is a highly sensitive file. Anyone who has access to your master key, will have access to your encrypted credentials. In order to safely upload the &lt;code&gt;config/master.key&lt;/code&gt; file to App Engine, we are going to encrypt it the same way we encrypted the &lt;code&gt;service-account.json&lt;/code&gt; file earlier:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;$ travis encrypt-file config/master.key config/master.key.enc --com
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Again, the terminal output will contain the command we need to run to decrypt &lt;code&gt;config/master.key&lt;/code&gt;. Copy the command and add it as the first command to a new step called &lt;code&gt;before_deploy&lt;/code&gt; in &lt;code&gt;.travis.yml&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;before_deploy:
    - openssl aes-256-cbc -K $encrypted_ENCRYPTION_ID_key -iv $encrypted_ENCRYPTION_ID_iv -in config/master.key.enc -out config/master.key -d
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;preparing-assets&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#preparing-assets&quot; aria-label=&quot;preparing assets permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Preparing assets&lt;/h3&gt;&lt;p&gt;In order to precompile production assets, add the following command to &lt;code&gt;before_deploy&lt;/code&gt; in &lt;code&gt;.travis.yml&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;RAILS_ENV=production bin/rails assets:precompile
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To learn more about how prepare your asset pipeline for production, see &lt;a href=&quot;https://guides.rubyonrails.org/asset_pipeline.html#in-production&quot;&gt;the official asset pipeline documentation&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Here is what our final &lt;code&gt;before_deploy&lt;/code&gt; step should look like:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;before_deploy:
    - openssl aes-256-cbc -K $encrypted_8ad82cc635a3_key -iv $encrypted_8ad82cc635a3_iv -in config/master.key.enc -out config/master.key -d
    - RAILS_ENV=production bin/rails assets:precompile
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;writing-the-deploy-script&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#writing-the-deploy-script&quot; aria-label=&quot;writing the deploy script permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Writing the deploy script&lt;/h3&gt;&lt;p&gt;Travis CI has a ready-made App Engine integration that we could use to deploy our Rails app (read more about it &lt;a href=&quot;https://docs.travis-ci.com/user/deployment/google-app-engine/&quot;&gt;here&lt;/a&gt;). However, this integration will install a second version of Google Cloud SDK instead of using our existing, cached version. In order to keep our Travis CI builds fast, we will write our own deploy script and make use of our existing Google Cloud SDK installation instead.&lt;/p&gt;&lt;p&gt;Why do we need our own Google Cloud SDK version? Later on in this guide we will start running migration tasks on App Engine using the &lt;code&gt;appengine&lt;/code&gt; gem. This step requires our own gcloud installation.&lt;/p&gt;&lt;p&gt;Add a new file &lt;code&gt;deploy&lt;/code&gt; to the &lt;code&gt;bin&lt;/code&gt; directory with the following content:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;#!/bin/bash
gcloud -q app deploy app.yaml
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This gcloud command deploys a new version to App Engine using the &lt;code&gt;app.yaml&lt;/code&gt; config file. The &lt;code&gt;-q&lt;/code&gt; flag is there to disable user prompts.&lt;/p&gt;&lt;p&gt;Make this file executable by running:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;$ chmod +x ./bin/deploy
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Add a new &lt;code&gt;deploy&lt;/code&gt; step to &lt;code&gt;.travis.yml&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;deploy:
  provider: script
  script: ./bin/deploy
  skip_cleanup: true
  on:
    branch: master
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;provider: script&lt;/code&gt; tells Travis CI to use a custom deploy script. &lt;code&gt;script: ./bin/deploy&lt;/code&gt; tells Travis CI where to find this deploy script. &lt;code&gt;skip_cleanup: true&lt;/code&gt; prevents Travis CI from removing the files generated during the previous steps (mainly &lt;code&gt;config/master.key&lt;/code&gt; and precompiled assets). &lt;code&gt;branch: master&lt;/code&gt; prevents this deploy step from being run on any other branch than our main branch.&lt;/p&gt;&lt;h3 id=&quot;first-deployment&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#first-deployment&quot; aria-label=&quot;first deployment permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;First deployment&lt;/h3&gt;&lt;p&gt;Commit your changes and push them to GitHub. If you go see your build at &lt;a href=&quot;https://travis-ci.com/&quot;&gt;https://travis-ci.com/&lt;/a&gt; you should see it running successfully. See the log from gcloud&amp;#x27;s deploy command to see where you can preview your app (the URL will have the format of &lt;code&gt;YOUR_PROJECT_ID.YOUR_REGION_ID.r.appspot.com&lt;/code&gt;).&lt;/p&gt;&lt;p&gt;If you go preview your app right now, you will probably encounter an error page. This is because your app is still missing a database. Let&amp;#x27;s set up one in the next chapter.&lt;/p&gt;&lt;h2 id=&quot;setting-up-postgresql-with-cloud-sql&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#setting-up-postgresql-with-cloud-sql&quot; aria-label=&quot;setting up postgresql with cloud sql permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Setting up PostgreSQL with Cloud SQL&lt;/h2&gt;&lt;h3 id=&quot;creating-a-database&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#creating-a-database&quot; aria-label=&quot;creating a database permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Creating a database&lt;/h3&gt;&lt;p&gt;Cloud SQL is GCP&amp;#x27;s product name for its managed database instances. This guide assumes you are using PostgreSQL but it&amp;#x27;s also possible to use Cloud SQL with MySQL and SQL Server.&lt;/p&gt;&lt;p&gt;In the Cloud console, navigate to &lt;code&gt;SQL&lt;/code&gt; and click &lt;code&gt;Create instance&lt;/code&gt;. Select PostgreSQL and continue to the creation form.&lt;/p&gt;&lt;p&gt;Choose an ID for your database instance (this ID will be public) and a password for the default &lt;code&gt;postgres&lt;/code&gt; user. Save the password somewhere safe for later use.&lt;/p&gt;&lt;p&gt;Select a region that&amp;#x27;s closest to your App Engine region (according to Google Cloud&amp;#x27;s documentation, it&amp;#x27;s okay to leave the zone as &lt;code&gt;Any&lt;/code&gt; in most cases).&lt;/p&gt;&lt;p&gt;Finally, choose a PostgreSQL version you want to use and click &lt;code&gt;Create&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;You can also configure things like your backup strategy and machine type using this form but this beyond the scope of this guide; the default configuration should work for you just fine especially if you are working with a demo app.&lt;/p&gt;&lt;p&gt;If you are experiencing problems setting up Cloud SQL or if you want to create a PostgreSQL instance using gcloud, you can check out Google Cloud&amp;#x27;s documentation on creating PostgreSQL instances &lt;a href=&quot;https://cloud.google.com/sql/docs/postgres/create-instance&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Wait for GCP to create your new database instance and after that, select it from the list of instances. Click &lt;code&gt;Databases&lt;/code&gt; and &lt;code&gt;Create database&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Give the database a name such as &lt;code&gt;myapp_production&lt;/code&gt; and click &lt;code&gt;Create&lt;/code&gt;.&lt;/p&gt;&lt;h3 id=&quot;configuring-database-connections&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#configuring-database-connections&quot; aria-label=&quot;configuring database connections permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Configuring database connections&lt;/h3&gt;&lt;p&gt;We need to tell our Rails app how to connect to our new PostgreSQL instance. Part of this configuration contains sensitive information (the password for the &lt;code&gt;postgres&lt;/code&gt; user) and we need to keep it private since we are uploading it to our possibly public GitHub repo.&lt;/p&gt;&lt;p&gt;As mentioned in chapter &lt;a href=&quot;#managing-secrets&quot;&gt;Managing secrets&lt;/a&gt;, we can store encrypted secrets inside &lt;code&gt;config/credentials.yml.enc&lt;/code&gt; which we can then commit to our git history.&lt;/p&gt;&lt;p&gt;To add new secrets, open the credentials file by first running:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;$ bin/rails credentials:edit
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Add the username and password of your PostgreSQL instance to the file and save it:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;db:
  username: postgres
  password: &amp;lt;YOUR PASSWORD&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Rest of the configuration contains less sensitive information that we can include in &lt;code&gt;app.yaml&lt;/code&gt; as is.&lt;/p&gt;&lt;p&gt;Open &lt;code&gt;app.yaml&lt;/code&gt; and add two new environment variables, &lt;code&gt;DATABASE_NAME&lt;/code&gt; and &lt;code&gt;DATABASE_HOST&lt;/code&gt;, to it:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;runtime: ruby25
entrypoint: bundle exec rails server -p $PORT
env_variables:
  RAILS_ENV: production
  DATABASE_NAME: YOUR_DATABASE_NAME
  DATABASE_HOST: /cloudsql/YOUR_INSTANCE_CONNECTION_NAME
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can find the instance connection name of your PostgreSQL instance in Cloud console by navigating to &lt;code&gt;SQL&lt;/code&gt; and finding the column &lt;code&gt;Instance connection name&lt;/code&gt; from the instances list. The instance connection name should have the format of &lt;code&gt;PROJECT:REGION:INSTANCE_NAME&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;Now that we have credentials and environment variables set up, we can finally add them to our database configuration.&lt;/p&gt;&lt;p&gt;Open &lt;code&gt;config/database.yml&lt;/code&gt; and add the following production configuration to it:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;production:
  &amp;lt;&amp;lt;: *default
  database: &amp;lt;%= ENV[&amp;quot;DATABASE_NAME&amp;quot;] %&amp;gt;
  host: &amp;lt;%= ENV[&amp;quot;DATABASE_HOST&amp;quot;] %&amp;gt;
  username: &amp;lt;%= Rails.application.credentials.db.fetch(:username) %&amp;gt;
  password: &amp;lt;%= Rails.application.credentials.db.fetch(:password) %&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;At this point we need to also share our &lt;code&gt;config/master.key&lt;/code&gt; with Travis CI so that it will be able to read our database credentials during database config initialization. Travis CI won&amp;#x27;t actually use the production config, but it will try to parse it nonetheless.&lt;/p&gt;&lt;p&gt;Log in to &lt;a href=&quot;https://travis-ci.com/&quot;&gt;Travis CI&lt;/a&gt; and select your project. Navigate to the project settings and add a new environment variable &lt;code&gt;RAILS_MASTER_KEY&lt;/code&gt; with the value of the contents of your &lt;code&gt;config/master.key&lt;/code&gt; file.&lt;/p&gt;&lt;p&gt;Commit your changes to &lt;code&gt;config/credentials.yml.enc&lt;/code&gt;, &lt;code&gt;app.yaml&lt;/code&gt; and &lt;code&gt;config/database.yml&lt;/code&gt;, and push them to GitHub.&lt;/p&gt;&lt;h3 id=&quot;running-migrations&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#running-migrations&quot; aria-label=&quot;running migrations permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Running migrations&lt;/h3&gt;&lt;p&gt;In addition to creating a database in Cloud SQL and establishing a connection to it, we need to be able to run our migration files as we keep developing our Rails application.&lt;/p&gt;&lt;p&gt;Using the &lt;a href=&quot;https://github.com/GoogleCloudPlatform/appengine-ruby&quot;&gt;&lt;code&gt;appengine&lt;/code&gt; gem&lt;/a&gt;, we can run migration tasks on Google Cloud instead of connecting your computer to the production database and running them from your local environment.&lt;/p&gt;&lt;p&gt;First, add &lt;code&gt;appengine&lt;/code&gt; to your &lt;code&gt;Gemfile&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;gem &amp;#x27;appengine&amp;#x27;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Install the dependency by running&lt;/p&gt;&lt;pre&gt;&lt;code&gt;$ bundle
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After that, add a migration script to our &lt;code&gt;before_deploy&lt;/code&gt; step inside &lt;code&gt;.travis.yml&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;before_deploy:
    - openssl aes-256-cbc -K $encrypted_8ad82cc635a3_key -iv $encrypted_8ad82cc635a3_iv -in config/master.key.enc -out config/master.key -d
    - RAILS_ENV=production bin/rails assets:precompile
    - bundle exec rake appengine:exec -- bin/rails db:migrate
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can now try to add a new migration file and deploy your application.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Sidenote:&lt;/em&gt; At the time of writing, my migrations fail every now and then when I use this setup. However, whenever I restart a failed build on Travis, the migrations run successfully. I haven&amp;#x27;t unfortunately been able to figure out what&amp;#x27;s causing these occasional fails.&lt;/p&gt;&lt;h2 id=&quot;finishing-up&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#finishing-up&quot; aria-label=&quot;finishing up permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Finishing up&lt;/h2&gt;&lt;p&gt;We have now successfully deployed a Rails 6 application on Google App Engine Standard. Below you will find the full Travis CI file for reference. You can also find an example project running this setup at &lt;a href=&quot;https://github.com/jonaskay/rails-app-engine-standard&quot;&gt;https://github.com/jonaskay/rails-app-engine-standard&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;But before we finish up, let&amp;#x27;s clean up our GCP resources so that we won&amp;#x27;t end up with surprise costs.&lt;/p&gt;&lt;h3 id=&quot;clean-up&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#clean-up&quot; aria-label=&quot;clean up permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Clean-up&lt;/h3&gt;&lt;p&gt;The safest way to make sure that you have deleted all the billable resources is to delete your Cloud project.&lt;/p&gt;&lt;p&gt;You can find the official documentation on how to do this &lt;a href=&quot;https://cloud.google.com/resource-manager/docs/creating-managing-projects#shutting_down_projects&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;&lt;h3 id=&quot;full-travisyml-file&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#full-travisyml-file&quot; aria-label=&quot;full travisyml file permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Full .travis.yml file&lt;/h3&gt;&lt;pre&gt;&lt;code&gt;dist: xenial
language: ruby
cache:
  bundler: true
  yarn: true
  directories:
    - &amp;quot;node_modules&amp;quot;
    - &amp;quot;$HOME/google-cloud-sdk/&amp;quot;
services:
  - postgresql
before_install:
  - openssl aes-256-cbc -K $encrypted_9f3b5599b056_key -iv $encrypted_9f3b5599b056_iv -in service-account.json.enc -out service-account.json -d
  - if [ ! -d ${HOME}/google-cloud-sdk ]; then
      curl https://sdk.cloud.google.com | bash;
    fi
  - gcloud auth activate-service-account --key-file service-account.json
install:
  - gcloud config set project sandbox-jonaskay
  - bundle install --jobs=3 --retry=3 --deployment
  - nvm install
  - bin/yarn
before_script:
  - bin/rails db:prepare
script:
  - bin/rails test
before_deploy:
  - openssl aes-256-cbc -K $encrypted_8ad82cc635a3_key -iv $encrypted_8ad82cc635a3_iv -in config/master.key.enc -out config/master.key -d
  - RAILS_ENV=production bin/rails assets:precompile
  - bundle exec rake appengine:exec -- bin/rails db:migrate
deploy:
  provider: script
  script: ./bin/deploy
  skip_cleanup: true
  on:
    branch: master
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;useful-links&quot; style=&quot;position:relative&quot;&gt;&lt;a href=&quot;#useful-links&quot; aria-label=&quot;useful links permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg xmlns=&quot;http://www.w3.org/2000/svg&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot; width=&quot;20&quot; height=&quot;20&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Useful links&lt;/h2&gt;&lt;ul&gt;&lt;li&gt;A working demo app can be found at &lt;a href=&quot;https://github.com/jonaskay/rails-app-engine-standard&quot;&gt;https://github.com/jonaskay/rails-app-engine-standard&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cloud.google.com/appengine/docs/standard/ruby/quickstart&quot;&gt;Google&amp;#x27;s guide for running Ruby in the App Engine standard environment&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cloud.google.com/ruby/rails/appengine&quot;&gt;Google&amp;#x27;s guide for running Rails 5 in the App Engine flexible environment&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://cloud.google.com/sql/docs/postgres/create-instance&quot;&gt;Google&amp;#x27;s guide for creating PostgreSQL instances&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;</content:encoded></item></channel></rss>