Home > Database > Mysql Tutorial > Setting up a new Rails application with Docker & MySQL

Setting up a new Rails application with Docker & MySQL

Barbara Streisand
Release: 2025-01-04 18:36:44
Original
217 people have browsed it

I'm a senior software engineer at a mid-sized technology and data company. Historically I've worn a lot of hats: I've built customer acquisition flows, done database management, complex React work, crafted full-featured CMS for internal usage, built public-facing Golang API microservices from scratch, crafted API authentication systems, delivered on a variety of B2B and B2C products, been tech lead for multiple teams (at once), and more. I'm also currently pretty out of practice when it comes to building new Rails applications from scratch. So I figured I'd give it a shot!

This is a pretty basic tutorial but I've found a dearth of practical guides for this. That being said, I do want to shout out two tutorials that I heavily borrowed from to write this—consider this a synthesis of those posts plus some of my personal preferences: Tallan Groberg and Nícolas Iensen. We're going to eschew a lot of the details in favor of jumping in. I'm writing this using a brand new M4 Macbook Pro but the basics should carry over to most Mac or Linux environments.

We'll be building a simple Ruby application that uses Rails as the main framework, MySQL for a database (partly for its features and partly to add to the complexity of what I'm aiming for with this post), and Docker for virtualization and cross-platform compatibility. We are not building any models or controllers in this tutorial: it's all about the setup. By the end of the tutorial, you'll have a pretty classic Hello World app. You should be able to take this basic concept and apply it to any application you're building.

Getting started

First things first, this assumes some familiarity with the terminal and Unix-based computers. If that makes some degree of sense, you're going to need to install Docker and Homebrew (assuming you're on a Mac). If you're running zsh as your primary shell (most Macs are by default these days), you may need to add the following to your ~/.zshrc file in order to be able to run brew commands:

path+=/opt/homebrew/bin
Copy after login
Copy after login
Copy after login
Copy after login

Once you've saved the file, run source ~/.zshrc and you should be good!

A small note: commands prefixed with $ indicate commands that are run in your local shell (zsh or bash, most likely) while commands prefixed with # are run inside the Docker container. In all cases, the prefix should not be copied, it's just a visual indicator of a new line prompt.

First things first

Many developers put all their coding projects into a single directory (mine lives as a sibling to the Downloads and Documents directories and I creatively call it code). In the terminal, navigate to your equivalent directory and type the following commands:

$ mkdir my-app
$ cd my-app
Copy after login
Copy after login
Copy after login
Copy after login

Inside this new directory, we need a few new files. Create them with the following commands:

path+=/opt/homebrew/bin
Copy after login
Copy after login
Copy after login
Copy after login

The first, Dockerfile.dev will create your base Docker image, building on an existing image that installs the version of Ruby we'll be using for this project (the latest as of this writing, 3.3.6) and setting up some minor details about how that image should behave. The .dev portion of the filename indicates that this Dockerfile is only going to be used locally and not in a production environment. A command we run later in the tutorial will actually build a more robust production-ready Dockerfile for us and I want to be able to distinguish the two. Given the scope of this tutorial, we're not worried about such details. Add the following lines to said file:

$ mkdir my-app
$ cd my-app
Copy after login
Copy after login
Copy after login
Copy after login

Next is the docker-compose.yml file: its purpose is to coordinate a number of Docker containers together to ensure that the web application we're building has all the constituent pieces talking together: the web server, a database, potentially a Redis server, maybe an Elasticsearch server, etc. All these different elements would live in their own "virtual computer" and need to be wired up to speak to each other in a manner mimicking a production environment. Enough about the theoretical stuff, the important bit is we need to add some configuration code to the docker-compose.yml file:

$ touch Dockerfile.dev
$ touch docker-compose.yml
$ touch Gemfile
Copy after login
Copy after login
Copy after login

Don't worry about the details but it's basically saying "when you run this, you're going to be running an application called 'web' and it will try to build the main application with a dockerfile called 'Dockerfile.dev' and will map the internal port 3000 in network of the docker system to the port 3000 of the local computer it's running on. Oh, and also, spin up a database and allow them to talk to each other." Or something like that. If you're already running an application on port 3000, feel free to change the left-hand port number to anything you like.

Okay! Now we have a file that will build an image and another file that will run a container using that image and pop it into a little network it spins up. Nice! But...how do we do that?

Getting into it

In order to start mucking about, we need to actually get into the container we're building to do some stuff. We do that by running this:

FROM ruby:3.3.6

WORKDIR /usr/src/app

COPY . .
RUN bundle install
Copy after login
Copy after login

Now we're in the computer. The idea is that now we can run commands within the environment of the container without needing to have certain software installed on the computer we're using. Rails, for example, doesn't need to exist anywhere on your computer to be able to run it on a Docker container running on your computer. Pretty nifty.

Okay, now that we're in, let's install Rails:

services:
  db:
    image: mysql:5.7
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: app
      MYSQL_USER: user
      MYSQL_PASSWORD: password
    ports:
      - "3307:3306"
  web:
    build:
      context: .
      dockerfile: Dockerfile.dev
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - ".:/usr/src/app"
    ports:
      - "3000:3000"
    depends_on:
      - db
    links:
      - db
    environment:
      DB_USER: root
      DB_NAME: app
      DB_PASSWORD: password
      DB_HOST: db
Copy after login
Copy after login

And now let's create our application! We wanted to build this application using MySQL, so note its specification in the following command:

path+=/opt/homebrew/bin
Copy after login
Copy after login
Copy after login
Copy after login

This is going to take a second. You'll be asked if you want to overwrite the Gemfile. Press y to confirm. You'll be asked the same question for any other files that are generated by this command. Use the y/n keys accordingly to skip or accept the new versions.

Huzzah! We have our application's skeleton done! However, we're not actually done. We've got to address one important piece to get the database ready. And then, ideally, we should address an important security detail.

Setting up the Database

The first part of this section is maybe not super necessary if you're just doing something locally and aren't planning to deploy anything. Furthermore, there's a lot more to consider here and I think it's worth a separate tutorial to dig into some of the DB configuration and basic repository security—especially if your repository is public (no worries if it is, just be careful out there!).

With the previous command we ended up with a huge number of new files and directories. One of them is config/database.yml. For me, on line 12 is a block that looks like so:

$ mkdir my-app
$ cd my-app
Copy after login
Copy after login
Copy after login
Copy after login

Technically the above works. There's nothing "wrong" with it. But we can do better. The biggest issue is that our DB has no password. The next issue is that the DB has no name. Finally, the username is visible in plain text. Not my favorite. Let's change all that with the following (the first of the following is a new field, the second two should replace any existing values):

$ touch Dockerfile.dev
$ touch docker-compose.yml
$ touch Gemfile
Copy after login
Copy after login
Copy after login

You can also use the ENV.fetch("VARIABLE_NAME") { "fallback_value" } style. The difference between ENV["VARIABLE_NAME"] and ENV.fetch("VARIABLE_NAME") is that the former will return nil if it can't find an environment variable with the designated name while the latter can raise some warnings or errors (see this and this for more information about ENV.fetch).

With all that, and assuming you haven't quit the shell (you can use docker-compose run --service-ports web bash to get back in), we need to create a new database. Do that with the following command:

FROM ruby:3.3.6

WORKDIR /usr/src/app

COPY . .
RUN bundle install
Copy after login
Copy after login

Quit the Docker shell and in the local terminal environment run the following commands:

services:
  db:
    image: mysql:5.7
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: app
      MYSQL_USER: user
      MYSQL_PASSWORD: password
    ports:
      - "3307:3306"
  web:
    build:
      context: .
      dockerfile: Dockerfile.dev
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - ".:/usr/src/app"
    ports:
      - "3000:3000"
    depends_on:
      - db
    links:
      - db
    environment:
      DB_USER: root
      DB_NAME: app
      DB_PASSWORD: password
      DB_HOST: db
Copy after login
Copy after login

That's it! If you point your browser (or an API client like Postman) at localhost:3000, you should see the classic Rails startup page:

Setting up a new Rails application with Docker & MySQL

Adding some extremely simple security

We've got a working application! And it comes with a nice database ready for production operations (the default database Rails provides, SQLite, is great for hacking together basic ideas but it's not meant for production work and it's creator is a weirdo)! But a more robust database comes with some additional responsibilities.

As we saw earlier in this tutorial, we were tasked with providing three important values: the name of the database, a username, and a password for that user. As of now, we have 1 layer of abstraction: rather than just passing in raw string values to the database.yml, we're fetching those values from the Rails environment. So, where is the Rails environment getting those values? From the docker-compose.yml file!

But it leaves an important problem still to be solved: assuming we're going to use this code in production, we've included information that no one but the system administrators should have access to right in the code itself. That's not great. We should have an additional layer of abstraction that removes any direct references to certain valuable, theoretically comprising information.

Now, we have to actually GET those environment variables set up properly in our Ruby environment when it first spins up. We're going to do this in two steps, but feel free to do it in one if you're comfortable. First we need to stop directly referring to the DB secrets in the Rails project. We're doing that. Next we need to pipe them from Docker into Rails. Finally, we're going to abstract it even further by adding the secret values in from a file we're hiding from Git to better obscure this information from potential ne'er-do-wells.

Piping environment variables into Rails from Docker

We have a few options, but my go-to is to create an environment file where these values get stored. If you're working with a team, you can share this file between you via more furtive measures (GPG encryption is a classic) without risking of putting this information on the public internet. If you take a look at the .gitignore file that was likely created when you ran rails new a little while back, you'll notice there's a line item for any files in the root of the project that begin with .env. That's exactly what we want: a secret file that doesn't get added to the git tracking but where we can save important, top secret information in plain text. Let's do it:

path+=/opt/homebrew/bin
Copy after login
Copy after login
Copy after login
Copy after login

I added the .dev suffix just in case we end up wanting different environment files for development, production, and test environments. In that newly created file, let's add some values:

$ mkdir my-app
$ cd my-app
Copy after login
Copy after login
Copy after login
Copy after login

We're also going to need to update the docker-compose.yml file in order to actually use the new environment file. Under the web service, add the following:

$ touch Dockerfile.dev
$ touch docker-compose.yml
$ touch Gemfile
Copy after login
Copy after login
Copy after login

And that's that! Start up the application again with docker compose up and navigate to localhost:3000 to confirm that all is well.

The above is the detailed content of Setting up a new Rails application with Docker & MySQL. For more information, please follow other related articles on the PHP Chinese website!

source:dev.to
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Latest Articles by Author
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template