Tag: Scalable

Building a Scalable CSS Architecture With BEM and Utility Classes

Maintaining a large-scale CSS project is hard. Over the years, we’ve witnessed different approaches aimed at easing the process of writing scalable CSS. In the end, we all try to meet the following two goals:

  1. Efficiency: we want to reduce the time spent thinking about how things should be done and increase the time doing things.
  2. Consistency: we want to make sure all developers are on the same page.

For the past year and a half, I’ve been working on a component library and a front-end framework called CodyFrame. We currently have 220+ components. These components are not isolated modules: they’re reusable patterns, often merged into each other to create complex templates.

The challenges of this project have forced our team to develop a way of building scalable CSS architectures. This method relies on CSS globals, BEM, and utility classes.

I’m happy to share it! 👇

CSS Globals in 30 seconds

Globals are CSS files containing rules that apply crosswise to all components (e.g., spacing scale, typography scale, colors, etc.). Globals use tokens to keep the design consistent across all components and reduce the size of their CSS.

Here’s an example of typography global rules:

/* Typography | Global */ :root {   /* body font size */   --text-base-size: 1em; 
   /* type scale */   --text-scale-ratio: 1.2;   --text-xs: calc((1em / var(--text-scale-ratio)) / var(--text-scale-ratio));   --text-sm: calc(var(--text-xs) * var(--text-scale-ratio));   --text-md: calc(var(--text-sm) * var(--text-scale-ratio) * var(--text-scale-ratio));   --text-lg: calc(var(--text-md) * var(--text-scale-ratio));   --text-xl: calc(var(--text-lg) * var(--text-scale-ratio));   --text-xxl: calc(var(--text-xl) * var(--text-scale-ratio)); } 
 @media (min-width: 64rem) { /* responsive decision applied to all text elements */   :root {     --text-base-size: 1.25em;     --text-scale-ratio: 1.25;   } } 
 h1, .text-xxl   { font-size: var(--text-xxl, 2.074em); } h2, .text-xl    { font-size: var(--text-xl, 1.728em); } h3, .text-lg    { font-size: var(--text-lg, 1.44em); } h4, .text-md    { font-size: var(--text-md, 1.2em); } .text-base      { font-size: 1em; } small, .text-sm { font-size: var(--text-sm, 0.833em); } .text-xs        { font-size: var(--text-xs, 0.694em); }

BEM in 30 seconds

BEM (Blocks, Elements, Modifiers) is a naming methodology aimed at creating reusable components.

Here’s an example:

<header class="header">   <a href="#0" class="header__logo"><!-- ... --></a>   <nav class="header__nav">     <ul>       <li><a href="#0" class="header__link header__link--active">Homepage</a></li>       <li><a href="#0" class="header__link">About</a></li>       <li><a href="#0" class="header__link">Contact</a></li>     </ul>   </nav> </header>
  • A block is a reusable component
  • An element is a child of the block (e.g., .block__element)
  • A modifier is a variation of a block/element (e.g., .block--modifier, .block__element--modifier).

Utility classes in 30 seconds

A utility class is a CSS class meant to do only one thing. For example:

<section class="padding-md">   <h1>Title</h1>   <p>Lorem ipsum dolor sit amet consectetur adipisicing elit.</p> </section> 
 <style>   .padding-sm { padding: 0.75em; }   .padding-md { padding: 1.25em; }   .padding-lg { padding: 2em; } </style>

You can potentially build entire components out of utility classes:

<article class="padding-md bg radius-md shadow-md">   <h1 class="text-lg color-contrast-higher">Title</h1>   <p class="text-sm color-contrast-medium">Lorem ipsum dolor sit amet consectetur adipisicing elit.</p> </article>

You can connect utility classes to CSS globals:

/* Spacing | Global */ :root {   --space-unit: 1em;   --space-xs:   calc(0.5 * var(--space-unit));   --space-sm:   calc(0.75 * var(--space-unit));   --space-md:   calc(1.25 * var(--space-unit));   --space-lg:   calc(2 * var(--space-unit));   --space-xl:   calc(3.25 * var(--space-unit)); }  /* responsive rule affecting all spacing variables */ @media (min-width: 64rem) {   :root {     --space-unit:  1.25em; /* 👇 this responsive decision affects all margins and paddings */   } }
 /* margin and padding util classes - apply spacing variables */ .margin-xs { margin: var(--space-xs); } .margin-sm { margin: var(--space-sm); } .margin-md { margin: var(--space-md); } .margin-lg { margin: var(--space-lg); } .margin-xl { margin: var(--space-xl); }  .padding-xs { padding: var(--space-xs); } .padding-sm { padding: var(--space-sm); } .padding-md { padding: var(--space-md); } .padding-lg { padding: var(--space-lg); } .padding-xl { padding: var(--space-xl); }

A real-life example

Explaining a methodology using basic examples doesn’t bring up the real issues nor the advantages of the method itself.

Let’s build something together! 

We’ll create a gallery of card elements. First, we’ll do it using only the BEM approach, and we’ll point out the issues you may face by going BEM only. Next, we’ll see how Globals reduce the size of your CSS. Finally, we’ll make the component customizable introducing utility classes to the mix.

Here’s a look at the final result:

Let’s start this experiment by creating the gallery using only BEM:

<div class="grid">   <article class="card">     <a class="card__link" href="#0">       <figure>         <img class="card__img" src="/image.jpg" alt="Image description">       </figure> 
       <div class="card__content">         <h1 class="card__title-wrapper"><span class="card__title">Title of the card</span></h1> 
         <p class="card__description">Lorem ipsum dolor sit amet consectetur adipisicing elit. Tempore, totam?</p>       </div> 
       <div class="card__icon-wrapper" aria-hidden="true">         <svg class="card__icon" viewBox="0 0 24 24"><!-- icon --></svg>       </div>     </a>   </article> 
   <article class="card"><!-- card --></article>   <article class="card"><!-- card --></article>   <article class="card"><!-- card --></article> </div>

In this example, we have two components: .grid and .card. The first one is used to create the gallery layout. The second one is the card component.

First of all, let me point out the main advantages of using BEM: low specificity and scope.

/* without BEM */ .grid {} .card {} .card > a {} .card img {} .card-content {} .card .title {} .card .description {} 
 /* with BEM */ .grid {} .card {} .card__link {} .card__img {} .card__content {} .card__title {} .card__description {}

If you don’t use BEM (or a similar naming method), you end up creating inheritance relationships (.card > a).

/* without BEM */ .card > a.active {} /* high specificity */ 
 /* without BEM, when things go really bad */ div.container main .card.is-featured > a.active {} /* good luck with that 😦 */ 
 /* with BEM */ .card__link--active {} /* low specificity */

Dealing with inheritance and specificity in big projects is painful. That feeling when your CSS doesn’t seem to be working, and you find out it’s been overwritten by another class 😡! BEM, on the other hand, creates some kind of scope for your components and keeps specificity low.

But… there are two main downsides of using only BEM:

  1. Naming too many things is frustrating
  2. Minor customizations are not easy to do or maintain

In our example, to stylize the components, we’ve created the following classes:

.grid {} .card {} .card__link {} .card__img {} .card__content {} .card__title-wrapper {} .card__title {} .card__description {} .card__icon-wrapper {} .card__icon {}

The number of classes is not the issue. The issue is coming up with so many meaningful names (and having all your teammates use the same naming criteria).

For example, imagine you have to modify the card component by including an additional, smaller paragraph:

<div class="card__content">   <h1 class="card__title-wrapper"><span class="card__title">Title of the card</span></h1>   <p class="card__description">Lorem ipsum dolor...</p>   <p class="card__description card__description--small">Lorem ipsum dolor...</p> <!-- 👈 --> </div>

How do you call it? You could consider it a variation of the .card__description element and go for .card__description .card__description--small. Or, you could create a new element, something like .card__small, .card__small-p, or .card__tag. See where I’m going? No one wants to spend time thinking about class names. BEM is great as long as you don’t have to name too many things.

The second issue is dealing with minor customizations. For example, imagine you have to create a variation of the card component where the text is center-aligned.

You’ll probably do something like this:

<div class="card__content card__content--center"> <!-- 👈 -->   <h1 class="card__title-wrapper"><span class="card__title">Title of the card</span></h1>   <p class="card__description">Lorem ipsum dolor sit amet consectetur adipisicing elit. Tempore, totam?</p> </div> 
 <style>   .card__content--center { text-align: center; } </style>

One of your teammates, working on another component (.banner), is facing the same problem. They create a variation for their component as well:

<div class="banner banner--text-center"></div> 
 <style>   .banner--text-center { text-align: center; } </style>

Now imagine you have to include the banner component into a page. You need the variation where the text is aligned in the center. Without checking the CSS of the banner component, you may instinctively write something like banner banner--center in your HTML, because you always use --center when you create variations where the text is center-aligned. Not working! Your only option is to open the CSS file of the banner component, inspect the code, and find out what class should be applied to align the text in the center.

How long would it take, 5 minutes? Multiply 5 minutes by all the times this happens in a day, to you and all your teammates, and you realize how much time is wasted. Plus, adding new classes that do the same thing contributes to bloating your CSS.

CSS Globals and utility classes to the rescue

The first advantage of setting global styles is having a set of CSS rules that apply to all the components.

For example, if we set responsive rules in the spacing and typography globals, these rules will affect the grid and card components as well. In CodyFrame, we increase the body font size at a specific breakpoint; because we use “em” units for all margins and paddings, the whole spacing system is updated at once generating a cascade effect.

Spacing and typography responsive rules — no media queries on a component level 

As a consequence, in most cases, you won’t need to use media queries to increase the font size or the values of margins and paddings!

/* without globals */ .card { padding: 1em; } 
 @media (min-width: 48rem) {   .card { padding: 2em; }   .card__content { font-size: 1.25em; } } 
 /* with globals (responsive rules intrinsically applied) */ .card { padding: var(--space-md); }

Not just that! You can use the globals to store behavioral components that can be combined with all other components. For example, in CodyFrame, we define a .text-component class that is used as a “text wrapper.” It takes care of line height, vertical spacing, basic styling, and other things.

If we go back to our card example, the .card__content element could be replaced with the following:

<!-- without globals --> <div class="card__content">   <h1 class="card__title-wrapper"><span class="card__title">Title of the card</span></h1>   <p class="card__description">Lorem ipsum dolor sit amet consectetur adipisicing elit. Tempore, totam?</p> </div> 
 <!-- with globals --> <div class="text-component">   <h1 class="text-lg"><span class="card__title">Title of the card</span></h1>   <p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Tempore, totam?</p> </div>

The text component will take care of the text formatting, and make it consistent across all the text blocks in your project. Plus, we’ve already eliminated a couple of BEM classes.

Finally, let’s introduce the utility classes to the mix!

Utility classes are particularly useful if you want the ability to customize the component later on without having to check its CSS.

Here’s how the structure of the card component changes if we swap some BEM classes with utility classes:

<article class="card radius-lg">   <a href="#0" class="block color-inherit text-decoration-none">     <figure>       <img class="block width-100%" src="image.jpg" alt="Image description">     </figure> 
     <div class="text-component padding-md">       <h1 class="text-lg"><span class="card__title">Title of the card</span></h1>       <p class="color-contrast-medium">Lorem ipsum dolor sit amet consectetur adipisicing elit. Tempore, totam?</p>     </div> 
     <div class="card__icon-wrapper" aria-hidden="true">       <svg class="icon icon--sm color-white" viewBox="0 0 24 24"><!-- icon --></svg>     </div>   </a> </article>

The number of BEM (component) classes has shrunk from 9 to 3:

.card {} .card__title {} .card__icon-wrapper {}

That means you won’t deal much with naming things. That said, we can’t avoid the naming issue entirely: even if you create Vue/React/SomeOtherFramework components out of utility classes, you still have to name the components.

All the other BEM classes have been replaced by utility classes. What if you have to make a card variation with a bigger title? Replace text-lg with text-xl. What if you want to change the icon color? Replace color-white with color-primary. How about aligning the text in the center? Add text-center to the text-component element. Less time thinking, more time doing!

Why don’t we just use utility classes?

Utility classes speed-up the design process and make it easier to customize things. So why don’t we forget about BEM and use only utility classes? Two main reasons:

By using BEM together with utility classes, the HTML is easier to read and customize.

Use BEM for:

  • DRY-ing the HTML from the CSS you don’t plan on customizing (e.g., behavioral CSS-like transitions, positioning, hover/focus effects),
  • advanced animations/effects.

Use utility classes for:

  • the “frequently-customized” properties, often used to create component variations (like padding, margin, text-alignment, etc.),
  • elements that are hard to identify with a new, meaningful class name (e.g., you need a parent element with a position: relative → create <div class="position-relative"><div class="my-component"></div></div>).

Example: 

<!-- use only Utility classes --> <article class="position-relative overflow-hidden bg radius-lg transition-all duration-300 hover:shadow-md col-6@sm col-4@md">   <!-- card content --> </article> 
 <!-- use BEM + Utility classes --> <article class="card radius-lg col-6@sm col-4@md">   <!-- card content --> </article>

For these reasons, we suggest that you don’t add the !important rule to your utility classes. Using utility classes doesn’t need to be like using a hammer. Do you think it would be beneficial to access and modify a CSS property in the HTML? Use a utility class. Do you need a bunch of rules that won’t need editing? Write them in your CSS. This process doesn’t need to be perfect the first time you do it: you can tweak the component later on if required. It may sound laborious “having to decide” but it’s quite straightforward when you put it to practice.

Utility classes are not your best ally when it comes to creating unique effects/animations.

Think about working with pseudo-elements, or crafting unique motion effects that require custom bezier curves. For those, you still need to open your CSS file.

Consider, for example, the animated background effect of the card we’ve designed. How hard would it be to create such an effect using utility classes?

The same goes for the icon animation, which requires animation keyframes to work:

.card:hover .card__title {   background-size: 100% 100%; } 
 .card:hover .card__icon-wrapper .icon {   animation: card-icon-animation .3s; } 
 .card__title {   background-image: linear-gradient(transparent 50%, alpha(var(--color-primary), 0.2) 50%);   background-repeat: no-repeat;   background-position: left center;   background-size: 0% 100%;   transition: background .3s; } 
 .card__icon-wrapper {   position: absolute;   top: 0;   right: 0;   width: 3em;   height: 3em;   background-color: alpha(var(--color-black), 0.85);   border-bottom-left-radius: var(--radius-lg);   display: flex;   justify-content: center;   align-items: center; } 
 @keyframes card-icon-animation {   0%, 100% {     opacity: 1;     transform: translateX(0%);   }   50% {     opacity: 0;     transform: translateX(100%);   }   51% {     opacity: 0;     transform: translateX(-100%);   } }

Final result

Here’s the final version of the cards gallery. It also includes grid utility classes to customize the layout.

File structure

Here’s how the structure of a project built using the method described in this article would look like:

project/ └── main/     ├── assets/     │   ├── css/     │   │   ├── components/     │   │   │   ├── _card.scss     │   │   │   ├── _footer.scss     │   │   │   └── _header.scss     │   │   ├── globals/     │   │   │   ├── _accessibility.scss     │   │   │   ├── _breakpoints.scss     │   │   │   ├── _buttons.scss     │   │   │   ├── _colors.scss     │   │   │   ├── _forms.scss     │   │   │   ├── _grid-layout.scss     │   │   │   ├── _icons.scss     │   │   │   ├── _reset.scss     │   │   │   ├── _spacing.scss     │   │   │   ├── _typography.scss     │   │   │   ├── _util.scss     │   │   │   ├── _visibility.scss     │   │   │   └── _z-index.scss     │   │   ├── _globals.scss     │   │   ├── style.css     │   │   └── style.scss     │   └── js/     │       ├── components/     │       │   └── _header.js     │       └── util.js     └── index.html

You can store the CSS (or SCSS) of each component into a separate file (and, optionally, use PostCSS plugins to compile each new /component/componentName.css file into style.css). Feel free to organize the globals as you prefer; you could also create a single globals.css file and avoid separating the globals in different files.

Conclusion

Working on large-scale projects requires a solid architecture if you want to open your files months later and don’t get lost. There are many methods out there that tackle this issue (CSS-in-JS, utility-first, atomic design, etc.).

The method I’ve shared with you today relies on creating crosswise rules (globals), using utility classes for rapid development, and BEM for modular (behavioral) classes.

You can learn in more detail about this method on CodyHouse. Any feedback is welcome!

The post Building a Scalable CSS Architecture With BEM and Utility Classes appeared first on CSS-Tricks.

CSS-Tricks

, , , ,

I Spun up a Scalable WordPress Server Environment with Trellis, and You Can, Too

A few years back, my fledgling website design agency was starting to take shape; however, we had one problem: managing clients’ web servers and code deployments. We were unable to build a streamlined process of provisioning servers and maintaining operating system security patches. We had the development cycle down pat, but server management became the bane of our work. We also needed tight control over each server depending on a site’s specific needs. Also, no, shared hosting was not the long term solution.

I began looking for a prebuilt solution that could solve this problem but came up with no particular success. At first, I manually provisioned servers. This process quickly proved to be both monotonous and prone to errors. I eventually learned Ansible and created a homegrown conglomeration of custom Ansible roles, bash scripts and Ansible Galaxy roles that further simplified the process — but, again, there were still many manual steps needed to take before the server was 100%.

I’m not a server guy (nor do I pretend to be one), and at this point, it became apparent that going down this path was not going to end well in the long run. I was taking on new clients and needed a solution, or else I would risk our ability to be sustainable, let alone grow. I was spending gobs of time typing arbitrary sudo apt-get update commands into a shell when I should have been managing clients or writing code. That’s not to mention I was also handling ongoing security updates for the underlying operating system and its applications.

Tell me if any of this sounds familiar.

Serendipitously, at this time, the team at Roots had released Trellis for server provisioning; after testing it out, things seemed to fall into place. A bonus is that Trellis also handles complex code deployments, which turned out to be something else I needed as most of the client sites and web applications that we built have a relatively sophisticated build process for WordPress using Composer, npm, webpack, and more. Better yet, it takes just minutes to jumpstart a new project. After spending hundreds of hours perfecting my provisioning process with Trellis, I hope to pass what I’ve learned onto you and save you all the hours of research, trials, and manual work that I wound up spending.

A note about Bedrock

We’re going to assume that your WordPress project is using Bedrock as its foundation. Bedrock is maintained by the same folks who maintain Trellis and is a “WordPress boilerplate with modern development tools, easier configuration, and an improved folder structure.” This post does not explicitly explain how to manage Bedrock, but it is pretty simple to set up, which you can read about in its documentation. Trellis is natively designed to deploy Bedrock projects.

A note about what should go into the repo of a WordPress site

One thing that this entire project has taught me is that WordPress applications are typically just the theme (or the child theme in the parent/child theme relationship). Everything else, including plugins, libraries, parent themes and even WordPress itself are just dependencies. That means that our version control systems should typically include the theme alone and that we can use Composer to manage all of the dependencies. In short, any code that is managed elsewhere should never be versioned. We should have a way for Composer to pull it in during the deployment process. Trellis gives us a simple and straightforward way to accomplish this.

Getting started

Here are some things I’m assuming going forward:

  • The code for the new site in the directory ~/Sites/newsite
  • The staging URL is going to be https://newsite.statenweb.com
  • The production URL is going to be https://newsite.com
  • Bedrock serves as the foundation for your WordPress application
  • Git is used for version control and GitHub is used for storing code. The repository for the site is: git@github.com:statenweb/newsite.git

I am a little old school in my local development environment, so I’m foregoing Vagrant for local development in favor of MAMP. We won’t go over setting up the local environment in this article.

I set up a quick start bash script for MacOS to automate this even further.

The two main projects we are going to need are Trellis and Bedrock. If you haven’t done so already, create a directory for the site (mkdir ~/Sites/newsite) and clone both projects from there. I clone Trellis into a /trellis directory and Bedrock into the /site directory:

cd ~/Sites/newsite git clone git@github.com:roots/trellis.git git clone git@github.com:roots/bedrock.git site cd trellis rm -rf .git cd ../site rm -rf .git

The last four lines enable us to version everything correctly. When you version your project, the repo should contain everything in ~/Sites/newsite.

Now, go into trellis and make the following changes:

First, open ~/Sites/newsite/trellis/ansible.cfg and add these lines to the bottom of the [defaults] key:

vault_password_file = .vault_pass host_key_checking = False

The first line allows us to use a .vault_pass file to encrypt all of our vault.yml files which are going to store our passwords, sensitive data, and salts.

The second host_key_checking = False can be omitted for security as it could be considered somewhat dangerous. That said, it’s still helpful in that we do not have to manage host key checking (i.e., typing yes when prompted).

Ansible vault password

Photo by Micah Williams on Unsplash

Next, let’s create the file ~/Sites/newsite/trellis/.vault_pass and enter a random hash of 64 characters in it. We can use a hash generator to create that (see here for example). This file is explicitly ignored in the default .gitignore, so it will (or should!) not make it up to the source control. I save this password somewhere extremely secure. Be sure to run chmod 600 .vault_pass to restrict access to this file.

The reason we do this is so we can store encrypted passwords in the version control system and not have to worry about exposing any of the server’s secrets. The main thing to call out is that the .vault_pass file is (and should not be) not committed to the repo and that the vault.yml file is properly encrypted; more on this in the “Encrypting the Secret Variables” section below.

Setting up target hosts

Photo by N. on Unsplash

Next, we need to set up our target hosts. The target host is the web address where Trellis will deploy our code. For this tutorial, we are going to be configuring newsite.com as our production target host and newsite.statenweb.com as our staging target host. To do this, let’s first update the production servers address in the production host file, stored in ~/Sites/newsite/trellis/hosts/production to:

[production] newsite.com  [web] newsite.com

Next, we can update the staging server address in the staging host file, which is stored in ~/Sites/newsite/trellis/hosts/staging to:

[staging] newsite.statenweb.com  [web] newsite.statenweb.com

Setting up GitHub SSH Keys

For deployments to be successful, SSH keys need to be working. Trellis takes advantage of how GitHub’ exposes all public (SSH) keys so that you do not need to add keys manually. To set this up go into the group_vars/all/users.yml and update both the web_user and the admin_user object’s keys value to include your GitHub username. For example:

users:   - name: '{{ web_user }}' 		groups: 		  - '{{ web_group }}' 		keys: 		  - https://github.com/matgargano.keys   - name: '{{ admin_user }}' 		groups: 		  - sudo 		keys: 		  - https://github.com/matgargano.keys

Of course, all of this assumes that you have a GitHub account with all of your necessary public keys associated with it.

Site Meta

We store essential site information in:

  • ~/Sites/newsite/trellis/group_vars/production/wordpress_sites.yml for production
  • ~/Sites/newsite/trellis/group_vars/staging/wordpress_sites.yml for staging.

Let’s update the following information for our staging wordpress_sites.yml:

wordpress_sites: 	newsite.statenweb.com: 		site_hosts: 		  - canonical: newsite.statenweb.com 		local_path: ../site  		repo: git@github.com:statenweb/newsite.git 		repo_subtree_path: site  		branch: staging 		multisite: 			enabled: false 		ssl: 			enabled: true 			provider: letsencrypt 		cache: 			enabled: false

This file is saying that we:

  • removed the site hosts redirects as they are not needed for staging
  • set the canonical site URL (newsite.statenweb.com) for the site key (newsite.statenweb.com)
  • defined the URL for the repository
  • the git repo branch that gets deployed to this target is staging, i.e., we are using a separate branch named staging for our staging site
  • enabled SSL (set to true), which will also install an SSL certificate when the box provisions

Let’s update the following information for our production wordpress_sites.yml:

wordpress_sites: 	newsite.com: 		site_hosts: 		  - canonical: newsite.com 				redirects: 				  - www.newsite.com 		local_path: ../site # path targeting local Bedrock site directory (relative to Ansible root) 		repo: git@github.com:statenweb/newsite.git 		repo_subtree_path: site  		branch: master 		multisite: 			enabled: false 		ssl: 			enabled: true 			provider: letsencrypt 		cache: 			enabled: false

Again, what this translates to is that we:

  • set the canonical site URL (newsite.com) for the site key (newsite.com)
  • set a redirect for www.newsite.com
  • defined the URL for the repository
  • the git repo branch that gets deployed to this target is master, i.e., we are using a separate branch named master for our production site
  • enabled SSL (set to true), which will install an SSL certificate when you provision the box

In the wordpress_sites.yml you can further configure your server with caching, which is beyond the scope of this guide. See Trellis’ documentation on FastCGI Caching for more information.

Secret Variables

Photo by Kristina Flour on Unsplash

There are going to be several secret pieces of information for both our staging and production site including the root user password, MySQL root password, site salts, and more. As referenced previously, Ansible Vault and using .vault_pass file makes this a breeze.

We store this secret site information in:

  • ~/Sites/newsite/trellis/group_vars/production/vault.yml for production
  • ~/Sites/newsite/trellis/group_vars/staging/vault.yml for staging

Let’s update the following information for our staging vault.yml:

vault_mysql_root_password: pK3ygadfPHcLCAVHWMX  vault_users: 	- name: "{{ admin_user }}" 		password: QvtZ7tdasdfzUmJxWr8DCs 		salt: "heFijJasdfQbN8bA3A"  vault_wordpress_sites: 	newsite.statenweb.com: 		env: 			auth_key: "Ab$ YTlX%:Qt8ij/99LUadfl1:U]m0ds@N<3@x0LHawBsO$ (gdrJQm]@alkr@/sUo.O" 			secure_auth_key: "+>Pbsd:|aiadf50;1Gz;.Z{nt%Qvx.5m0]4n:L:h9AaexLR{1B6.HeMH[w4$ >H_" 			logged_in_key: "c3]7HixBkSC%}-fadsfK0yq{HF)D#1S@Rsa`i5aW^jW+W`8`e=&PABU(s&JH5oPE" 			nonce_key: "5$ vig.yGqWl3G-.^yXD5.ddf/BsHx|i]>h=mSy;99ex*Saj<@lh;3)85D;#|RC=" 			auth_salt: "Wv)[t.xcPsA}&/]rhxldafM;h(FSmvR]+D9gN9c6{*hFiZ{]{,#b%4Um.QzAW+aLz" 			secure_auth_salt: "e4dz}_x)DDg(si/8Ye&U.p@pB}NzHdfQccJSAh;?W)>JZ=8:,i?;j$ bwSG)L!JIG" 			logged_in_salt: "DET>c?m1uMAt%hj3`8%_emsz}EDM7R@44c0HpAK(pSnRuzJ*WTQzWnCFTcp;,:44" 			nonce_salt: "oHB]MD%RBla*#x>[UhoE{hm{7j#0MaRA#fdQcdfKe]Y#M0kQ0F/0xe{cb|g,h.-m"

Now, let’s update the following information for our production vault.yml:

vault_mysql_root_password: nzUMN4zBoMZXJDJis3WC vault_users: 	- name: "{{ admin_user }}" 		password: tFxea6ULFM8CBejagwiU 		salt: "9LgzE8phVmNdrdtMDdvR" vault_wordpress_sites: 	newsite.com: 		env: 			db_password: eFKYefM4hafxCFy3cash 			# Generate your keys here: https://roots.io/salts.html 			auth_key: "|4xA-:Pa=-rT]&!-(%*uKAcd</+m>ix_Uv,`/(7dk1+;b|ql]42gh&HPFdDZ@&of" 			secure_auth_key: "171KFFX1ztl+1I/P$ bJrxi*s;}.>S:{^-=@*2LN9UfalAFX2Nx1/Q&i&LIrI(BQ[" 			logged_in_key: "5)F+gFFe}}0;2G:k/S>CI2M*rjCD-mFX?Pw!1o.@>;?85JGu}#(0#)^l}&/W;K&D" 			nonce_key: "5/[Zf[yXFFgsc#`4r[kGgduxVfbn::<+F<$ jw!WX,lAi41#D-Dsaho@PVUe=8@iH" 			auth_salt: "388p$ c=GFFq&hw6zj+T(rJro|V@S2To&dD|Q9J`wqdWM&j8.KN]y?WZZj$ T-PTBa" 			secure_auth_salt: "%Rp09[iM0.n[ozB(t;0vk55QDFuMp1-=+F=f%/Xv&7`_oPur1ma%TytFFy[RTI,j" 			logged_in_salt: "dOcGR-m:%4NpEeSj>?A8%x50(d0=[cvV!2x`.vB|^#G!_D-4Q>.+1K!6FFw8Da7G" 			nonce_salt: "rRIHVyNKD{LQb$ uOhZLhz5QX}P)QUUo!Yw]+@!u7WB:INFFYI|Ta5@G,j(-]F.@4"

The essential lines for both are that:

  • The site key must match the key in wordpress_sites.yml we are using newsite.statenweb.com: for staging and newsite.com: for production
  • I randomly generated vault_mysql_root_password, password, salt, db_password, and db_password. I used Roots’ helper to generate the salts.

I typically use Gmail’s SMTP servers using the Post SMTP plugin, so there’s no need for me to edit the ~/Sites/newsite/group_vars/all/vault.yml.

Encrypting the Secret Variables

Photo by Markus Spiske on Unsplash

As previously mentioned we use Ansible Vault to encrypt our vault.yml files. Here’s how to encrypt the files and make them ready to be stored in our version control system:

cd ~/Sites/newsite/trellis ansible-vault encrypt group_vars/staging/vault.yml group_vars/production/vault.yml

Now, if we open either ~/Sites/newsite/trellis/group_vars/staging/vault.yml or ~/Sites/newsite/trellis/group_vars/production/vault.yml, all we’ll get is garbled text. This is safe to be stored in a repository as the only way to decrypt it is to use the .vault_pass. It goes without saying to make extra sure that the .vault_pass itself does not get committed to the repository.

A note about compiling, transpiling, etc.

Another thing that’s out of scope is setting up Trellis deployments to handle a build process using build tools such as npm and webpack. This is example code to handle a custom build that could be included in ~/Sites/newsite/trellis/deploy-hooks/build-before.yml:

---  - 	args: 		chdir: "{{ project.local_path }}/web/app/themes/newsite" 	command: "npm install" 	connection: local 	name: "Run npm install"  - 	args: 		chdir: "{{ project.local_path }}/web/app/themes/newsite" 	command: "npm run build" 	connection: local 	name: "Compile assets for production"  - 	name: "Copy Assets" 	synchronize: 		dest: "{{ deploy_helper.new_release_path }}/web/app/themes/newsite/dist/" 		group: no 		owner: no 		rsync_opts: "--chmod=Du=rwx,--chmod=Dg=rx,--chmod=Do=rx,--chmod=Fu=rw,--chmod=Fg=r,--chmod=Fo=r" 		src: "{{ project.local_path }}/web/app/themes/newsite/dist/"

These are instructions that build assets and moves them into a directory that I explicitly decided not to version. I hope to write a follow-up guide that dives specifically into that.

Provision

Photo by Bill Jelen on Unsplash

I am not going to go in great detail about setting up the servers themselves, but I typically would go into DigitalOcean and spin up a new droplet. As of this writing, Trellis is written on Ubuntu 18.04 LTS (Bionic Beaver) which acts as the production server. In that droplet, I would add a public key that is also included in my GitHub account. For simplicity, I can use the same server as my staging server. This scenario is likely not what you would be using; maybe you use a single server for all of your staging sites. If that is the case, then you may want to pay attention to the passwords configured in ~/Sites/newsite/trellis/group_vars/staging/vault.yml.

At the DNS level, I would map the naked A record for newsite.com to the IP address of the newly created droplet. Then I’d map the CNAME www to @. Additionally, the A record for newsite.statenweb.com would be mapped to the IP address of the droplet (or, alternately, a CNAME record could be created for newsite.statenweb.com to newsite.com since they are both on the same box in this example).

After the DNS propagates, which can take some time, the staging box can be provisioned by running the following commands.

First off, it;’s possible you may need to run this before anything else:

ansible-galaxy install -r requirements.yml

Then, install the required Ansible Galaxy roles before proceeding:

cd ~/Sites/newsite/trellis ansible-playbook server.yml -e env=staging

Next up, provision the production box:

cd ~/Sites/newsite/trellis ansible-playbook server.yml -e env=production

Deploy

If all is set up correctly to deploy to staging, we can run these commands:

cd ~/Sites/newsite/trellis ansible-playbook deploy.yml -e "site=newsite.statenweb.com env=staging" -i hosts/staging

And, once this is complete, hit https://newsite.statenweb.com. That should bring up the WordPress installation prompt that provides the next steps to complete the site setup.

If staging is good to go, then we can issue the following commands to deploy to production:

cd ~/Sites/newsite/trellis ansible-playbook deploy.yml -e "site=newsite.com env=production" -i hosts/production

And, like staging, this should also prompt installation steps to complete when hitting https://newsite.com.

Go forth and deploy!

Hopefully, this gives you an answer to a question I had to wrestle with personally and saves you a ton of time and headache in the process. Having stable, secure and scalable server environments that take relatively little effort to spin up has made a world of difference in the way our team works and how we’re able to accommodate our clients’ needs.

While we’re technically done at this point, there are still further steps to take to wrap up your environment fully:

  • Add dependencies like plugins, libraries and parent themes to ~/Sites/newsite/composer.json and run composer update to grab the latest manifest versions.
  • Place the theme to ~/Sites/newsite/site/themes/. (Note that any WordPress theme can be used.)
  • Include any build processes you’d need (e.g. transpiling ES6, compiling SCSS, etc.) in one of the deployment hooks. (See the documentation for Trellis Hooks).

I have also been able to dive into enterprise-level continuous integration and continuous delivery, as well as how to handle premium plugins with Composer by running a custom Composer server, among other things, while incurring no additional cost. Hopefully, those are areas I can touch on in future posts.

Trellis provides a dead simple way to provision WordPress servers. Thanks to Trellis, long gone are the days of manually creating, patching and maintaining servers!

The post I Spun up a Scalable WordPress Server Environment with Trellis, and You Can, Too appeared first on CSS-Tricks.

CSS-Tricks

, , , , ,
[Top]