DJ-1: Creating a Rails API with Postgres and Docker
Beto Sardinha
Posted on January 13, 2023
Hello there to you who are reading this post for some reason!
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 or some version manager. In my case I'm using 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 and a few more rubocop-related ones to lint right into my code editor (I'm using vscode with the ruby and 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, 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).
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 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 containers. If you want, just follow this installation step by step.
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 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 configuration).
Dockerfile
# 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
#!/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
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
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
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, 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.
I also accessed the pgAdmin service to test that the database was created correctly.
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
Posted on January 13, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.