DJ-1: Creating a Rails API with Postgres and Docker
Tags: devjournal, webdev, rails, docker
Published: 2023-01-13 00:13:21

## Hello there to you who are reading this post for some reason!

![Me showing my code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m0dbz2le2uyodkkq5tmc.png)

I don't know how to start a development journal properly, it's the first time I've done something like this and it's also the first time I've written a full article in English. So I apologize for future language mistakes.

But I think you're here because maybe you want to create a small **Rails** project and learn a little more about **Docker**. And that's exactly what I want too! So this journal entry is my attempts to build a foundation for projects, and a step by step for you to build one to.

So let's go!

## Installing Ruby and Rails on your machine

First thing is to download and install ruby, you can either use the [default installation](https://www.ruby-lang.org/en/documentation/installation/) or some [version manager](https://www.ruby-toolbox.com/categories/ruby_version_management). In my case I'm using [rbenv](https://github.com/rbenv/rbenv), and if you're using linux I recommend it too, it's simple and I like it a lot.

For this project I'm using **ruby ​​3.2.0**, but feel free to use the most stable/latest version available.

After installing ruby, I installed the [rails gem](https://rubyonrails.org/) and a few more [rubocop-related](https://rubocop.org/) ones to lint right into my code editor (I'm using [vscode](https://code.visualstudio.com/) with the [ruby](https://marketplace.visualstudio.com/items?itemName=rebornix.Ruby) ​​and [ruby-rubocop](https://marketplace.visualstudio.com/items?itemName=misogi.ruby-rubocop) extensions).

```
gem install rails
gem install rubocop
gem install rubocop-rails
gem install rubocop-performance
```

## Creating a Rails API with Postgres

Where I work and in several other projects I use [PostgreSQL](https://www.postgresql.org/), I'm used to the behavior and the interface, so when creating this project I configured it to be the database. And since I'm working on an API project I set the flag for Rails **to not create front-end files** (I'll create a separate project to consume the API later, probably in [VueJS](https://vuejs.org/)).

I put the name as _articles-api_ for simplicity (since I'm not good with names).

```
rails new articles-api --api --database=postgresql
```

I also created the license file as [MIT](https://choosealicense.com/licenses/mit/) to specify anyone's right to download, use and modify the project.

## Using Docker to run the project

Despite being used to Postgres, I don't want to install it on my machine, nor use rails directly. And for many other compatibility reasons with other computers (and even for you to download and run this project) I decided to use [Docker](https://www.docker.com/) containers. If you want, just follow this [installation step by step](https://docs.docker.com/engine/).

I created my Dockerfile with the necessary settings for the Rails project, and an _entrypoint.sh_, which deletes the _server.pid_ file that sometimes is not removed if the container is not shut down correctly (generating an [error](https://www.chrisblunt.com/rails-on-docker-using-an-entrypoint-file-containers) when running it again).

Another point to be mentioned is the **BUNDLE_FROZEN** configuration, which will always force a comparison between the _Gemfile_ and the _Gemfile.lock_, and if the versions are different the project will not run (more a [security](https://stackoverflow.com/questions/68817050/using-bundle-frozen) configuration).

### **`Dockerfile`**
```docker
# Put the ruby ​​version you are using
FROM ruby:3.2.0

# Install the necessary libraries
RUN apt-get update -qq && apt-get install -y postgresql-client

# BUNDLE_FROZEN setting
RUN bundle config --global frozen 1

# Set working directory
WORKDIR /articles-api

# Copy and install the project gems
COPY Gemfile /articles-api/Gemfile
COPY Gemfile.lock /articles-api/Gemfile.lock
RUN bundle install

# Run entrypoint.sh to delete server.pid
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

# Listen on this specified network port
EXPOSE 3000

# Run rails server
CMD ["rails", "server", "-b", "0.0.0.0"]
```

### **`entrypoint.sh`**
```bash
#!/bin/bash
set -e

rm -f /articles-api/tmp/pids/server.pid

exec "$@"
```

## Setting up database variables

Before building the project, I corrected the database settings so that the project could connect to the database container. I put _host_, _username_ and _password_ as **environment variables**, which will be passed both in the container _compose file_ and in an _.env_ file in case it needs to be run locally at some point. Variables are in default scope, so development and test database have the same credentials.

### **`config/database.yml`**
```yaml
default: &default
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  host: <%= ENV['DBHOST'] %>
  username: <%= ENV['DBUSER'] %>
  password: <%= ENV['DBPASS'] %>

development:
  <<: *default
  database: articles_api_development

test:
  <<: *default
  database: articles_api_test

production:
  <<: *default
  database: articles_api_production
  username: articles_api
  password: <%= ENV["ARTICLES_API_DATABASE_PASSWORD"] %>
```

### **`.env`**
```properties
DBHOST=localhost
DBUSER=postgres
DBPASS=password
```

## Creating a docker-compose file

Finally, to be able to run the project, I created a _docker-compose.yml_ file containing the following services:

- A database using Postgres image;
- A database administrator with the pgAdmin image (optional);
- Rails running my project with the Dockerfile;

### **`docker-compose.yml`**
```yaml
version: '3.8'

services:
  articles-db:
    image: postgres
    container_name: articles-db
    volumes:
      - postgres:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: "articles_api_development"
      POSTGRES_USER: "postgres"
      POSTGRES_PASSWORD: "password"
    ports:
      - "5432:5432"
    networks:
      - articles-api-network

  articles-pgadmin:
    image: dpage/pgadmin4
    container_name: articles-pgadmin
    environment:
      PGADMIN_DEFAULT_EMAIL: "user@articles.com"
      PGADMIN_DEFAULT_PASSWORD: "password"
    ports:
      - "15432:80"
    depends_on:
      - articles-db
    networks:
      - articles-api-network

  articles-api:
    image: articles-api
    container_name: articles-api
    build: .
    environment:
      - DBHOST=articles-db
      - DBUSER=postgres
      - DBPASS=password
    volumes:
      - .:/articles-api
    ports:
      - "3000:3000"
    depends_on:
      - articles-db
    networks:
      - articles-api-network

networks:
  articles-api-network:
    driver: bridge

volumes:
  postgres:
```

Some points are interesting to talk about, all services have environment variables defined, with _articles-db_ and _articles-api_ **database access** variables, and _articles-pgadmin_ **access credentials** to the administrator panel. Both the API and pgAdmin services are dependent on the database, so if you upload any of them, the database service automatically uploads.

Another point is that I created a separate **network** for the services to communicate with, I just find it easier to find it by giving it a specific name. If you want to learn more about compose file tags I recommend the [official documentation](https://docs.docker.com/compose/gettingstarted/), Docker is just scary at first, with time you get used to it.

## Running the project

Finally, after so many configurations, I assembled the project's containers and loaded the services:

```
docker compose build
docker compose up
```
I entered the api service bash to perform the database migration:

```
docker compose exec articles-api bash
rails db:create
rails db:migrate
```

And right after restarting the services, I have confirmation that everything is working.

![Rails service](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8fdcb8nymkkfujtzuth0.png)

I also accessed the pgAdmin service to test that the database was created correctly.

![pgAdmin service](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/odup6zk9cluu45kbdtac.png)

## Conclusion

That's it for today! The project is working and ready for the next changes, I think after that I will make a new entry showing the process of creating a CI workflow with Github Actions.

If you have any questions or tips on how I can improve the project (or even this post), feel free to drop them in! My idea is that this development diary should be periodic.

## Link to the project

https://github.com/betosardinha/articles-api