This article is the continuation of Dockerは難しい😂.
Having worked on this project, from the very beginning, one of the more peculiar aspects of the project architecture was that of two Gemfiles existing in separate directories.
Now why might that be?
In this article we will cover one of the peculiarities of Nect_app and the decisions and thought processes behind the Docker configuration that lead up to it.
1
2
3
4
5
6
7
8
9
10
11
Nect_app
├── n_app
│ ├── ...
│ ├── Dockerfile
│ ├── Gemfile
│ └── Gemfile.lock
├── ...
├── compose.yaml
├── Dockerfile
├── Gemfile
└── Gemfile.lock
The above is the abridged directory structure for this webapp project.
Now that we have this more visual representation to go off of, we can go over the creation of this ruby on rails project.
One of the advantages of utilizing Docker is that we can create projects using the images of a certain programming language (i.e. Ruby) to base off of. With this, the setup of a project can be standardized, allowing for a smoother development cycle.
So then why don't we go over the reason for this directory structure.
1
2
3
4
5
6
7
8
9
10
11
Clone from repository
↓
Git initialization
↓
Run "$ docker compose run --rm webapp rails new ."
↓
Configurations for database.yml and Procfile.dev
↓
Initial database creation and migration
↓
Run docker container
As we can see we have the requirement of cloning the already existing repository from github and also having to make a new rails application for the setup process.
This is in the belief that in order for the cloned repository to work in the docker container we must initialize a new rails application first (but not do anything in particular with it hence the --rm flag)
The Gemfile in the Nect_app directory holds the following:
1
2
3
4
5
6
7
8
9
10
source 'https://rubygems.org'
gem 'rails', '~>7.1.3'
#add gem
gem "devise", "~> 4.9"
gem 'devise_invitable', '~> 2.0.0'
gem 'redcarpet'
gem 'rouge-rails'
gem 'caxlsx'
gem 'carrierwave', '~> 3.0'
gem 'dotenv-rails', groups: [:development, :test]
The Gemfile in the n_app directory holds the following:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
source "https://rubygems.org"
ruby "3.2.0"
# Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
gem "rails", "~> 7.1.3", ">= 7.1.3.2"
# The original asset pipeline for Rails [https://github.com/rails/sprockets-rails]
gem "sprockets-rails"
# Use mysql as the database for Active Record
gem "mysql2", "~> 0.5"
# Use the Puma web server [https://github.com/puma/puma]
gem "puma", ">= 5.0"
# Bundle and transpile JavaScript [https://github.com/rails/jsbundling-rails]
gem "jsbundling-rails"
# Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]
gem "turbo-rails"
# Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev]
gem "stimulus-rails"
# Bundle and process CSS [https://github.com/rails/cssbundling-rails]
gem "cssbundling-rails"
# Build JSON APIs with ease [https://github.com/rails/jbuilder]
gem "jbuilder"
# Use Redis adapter to run Action Cable in production
# gem "redis", ">= 4.0.1"
# Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis]
# gem "kredis"
# Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
# gem "bcrypt", "~> 3.1.7"
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem "tzinfo-data", platforms: %i[ windows jruby ]
# Reduces boot times through caching; required in config/boot.rb
gem "bootsnap", require: false
# Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
# gem "image_processing", "~> 1.2"
group :development, :test do
# See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
gem "debug", platforms: %i[ mri windows ]
end
group :development do
# Use console on exceptions pages [https://github.com/rails/web-console]
gem "web-console"
# Add speed badges [https://github.com/MiniProfiler/rack-mini-profiler]
# gem "rack-mini-profiler"
# Speed up commands on slow machines / big apps [https://github.com/rails/spring]
# gem "spring"
end
group :test do
# Use system testing [https://guides.rubyonrails.org/testing.html#system-testing]
gem "capybara"
gem "selenium-webdriver"
end
gem "devise", "~> 4.9"
gem 'devise_invitable', '~> 2.0.0'
gem 'redcarpet'
gem 'rouge-rails'
gem 'caxlsx'
gem 'carrierwave', '~> 3.0'
gem 'kaminari', '~> 1.2', '>= 1.2.1'
gem 'bootstrap5-kaminari-views', '~> 0.0.1'
gem 'dotenv-rails', groups: [:development, :test]
The Dockerfile in the Nect_app directory holds the following:
1
2
3
4
5
6
7
8
9
10
11
12
13
FROM ruby:3.2.0
ENV APP /n_app
ENV LANG C.UTF-8
ENV TZ Asia/Tokyo
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
RUN apt update -qq \
&& apt install -y build-essential mariadb-client nodejs \
&& npm install --global yarn
RUN yarn add @fortawesome/fontawesome-free @fortawesome/fontawesome-svg-core @fortawesome/free-brands-svg-icons @fortawesome/free-regular-svg-icons @fortawesome/free-solid-svg-icons
WORKDIR $APP
COPY Gemfile $APP/Gemfile
COPY Gemfile.lock $APP/Gemfile.lock
RUN bundle install
The Dockerfile in the n_app directory is auto generated when creating the ruby on rails application and it is mainly used for production so we will largely ignore what it contains this time.
As we can see, the two Gemfiles have the same gems that the developer wants to use in the application.
When building the image, the host machine's Nect_app/Gemfile is copied to the container's n_app/Gemfile when installing the bundles before the host machine's n_app is mounted, in which the n_app/Gemfile resides.
So in the end, the container's n_app/Gemfile copies host machine's Nect_app/Gemfile before the host machine's Nect_app/n_app/Gemfile is mounted on to the container.
1
2
3
4
5
6
7
8
9
10
11
12
# From Nect_app/Dockerfile
COPY Gemfile $APP/Gemfile
↓
$APP/Gemfile = Nect_app/Gemfile
# From compose.yaml
volumes:
- type: bind
source: ./n_app
target: /n_app
↓
$APP/Gemfile = n_app/Gemfile
I hope that you might have noticed a problem. A new developer who joins this web development team will simply pull the already in development repository from github. But if we look at the Environment Setup, somehow there is the assumption of a building an image for the first time that does not have a ruby on rails application, yet at the same time we have a bind mount that is referenced to the already existing ruby on rails application, that being of the host machine's Nect_app/n_app directory.
Not only is it largely unnecessary seeing as the burden of project initialization has already been taken care of, it also adds to the complication of which Gemfile should be updated (especially for newcomers who may or may not have been given such briefings).
In reality we need to run a helper container to update the volume gemdata
in order to make newly added gems installed properly. Refer to here for more detail.
The two Gemfiles are largely a result of misunderstanding on how a ruby on rails project should be shared among development team members (that being of the need to initialize an empty rails application in order to mount the pulled project from github).
Having two Gemfiles is ultimately not needed as they perform very similar functions and only adds to confusion regarding the project architecture and directory structure.
Whilst the usage of $ docker compose run --rm webapp rails new .
is appropriate when making a brand new ruby on rails application from just the Dockerfile
, compose.yaml
, Gemfile
, and Gemfile.lock
configurations, it is not needed when we already have a project to pull from github and work on immediately.
It is my personal belief that the directory structure as seen in the beginning of this article is flawed and must be rectified whenever possible.
As such, I am proposing the following:
As an aside the current configuration of having two Gemfiles will ultimately be the structure we will go along with. This article will be updated if these proposed changes are implemented.
This article was written as a culmination of the discussion regarding the structure of the Nect_app directory. This article is only really relevant to this project (that being Nect-Life) and its percieved peculiarities during development. Consider this article as an opinion piece of the author. This article is not representative of development policy going forward.