Container hóa phần mềm là việc đóng gói nó thành các đơn vị tiêu chuẩn để thuận tiện cho phát triển và triển khai. Các container gói mã ứng dụng cùng với tất cả các phụ thuộc của nó. Một container có thể hoạt động độc lập hoàn toàn; nó chứa một gói bao gồm phần mềm, môi trường chạy và thư viện hệ thống. Container giúp các nhà phát triển và nhóm vận hành đảm bảo phần mềm chạy giống nhau, bất kể môi trường. Bằng cách tách mã khỏi hạ tầng, các ứng dụng "container hóa" chạy giống nhau trong môi trường cục bộ, kiểm thử và production.
Điều kiện tiên quyết
Nếu bạn muốn làm theo, bạn sẽ cần một ứng dụng Rails chưa được docker hóa (đó là thuật ngữ dành riêng cho Docker của "container hóa"). Tôi sẽ sử dụng RailsWork, một dự án phụ đầy đủ tính năng vừa được khởi chạy. Đó là một trang tuyển dụng viết bằng Rails và triển khai trên Heroku, nhưng chưa được container hóa.
Ngoài ra, bạn cũng cần cài đặt Docker. Cách phổ biến là dùng Docker Desktop, có thể tải từ trang chủ chính thức.

Sau khi tải xuống, chạy bộ cài đặt. Sau khi chạy xong, nó sẽ nhắc bạn kéo ứng dụng vào thư mục Applications. Sau đó bạn phải khởi chạy ứng dụng từ thư mục Applications và cấp quyền đặc quyền mà nó yêu cầu. Kiểm tra cuối cùng để đảm bảo Docker cài đặt đúng: liệt kê các container đang chạy trên máy từ terminal bằng lệnh:
docker ps
Nếu Docker đã cài đặt (và bạn không chạy container nào), bạn sẽ nhận được danh sách trống chỉ có tiêu đề như sau:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
Dockerfile
Quan trọng là bắt đầu với thuật ngữ rõ ràng trước khi đi sâu.
Sau khi ứng dụng Rails được "Docker hóa", nó sẽ chạy trong một container. Container độc lập, có thể thay thế và thường được xây dựng lại.
Một container được xây dựng từ một image. Image là bản snapshot ảo của hệ thống tệp kèm metadata.
Một Dockerfile là mã nguồn mô tả cách tạo image. Dockerfile thường được đưa vào kho chứa của ứng dụng Docker hóa và theo dõi trong hệ thống kiểm soát phiên bản cùng với ứng dụng.
Tạo Dockerfile dễ dàng hơn bạn nghĩ! Docker cung cấp cú pháp đặc biệt trừu tượng hóa công việc khó khăn của container hóa. Trước hết, di chuyển đến thư mục gốc của ứng dụng muốn container hóa. Bây giờ bạn sẵn sàng, tốt nhất là tạo nhánh mới nếu dùng git. Bạn có thể tạo nhánh tên dockerize-this-app bằng lệnh:
git checkout -b dockerize-this-app
Tiếp theo, tạo Dockerfile và chỉ dẫn nó xây dựng image dựa trên ứng dụng Ruby. Có thể làm từ dòng lệnh:
echo "FROM ruby:3.0.0" > Dockerfile
Ở đây, chúng ta chỉ tạo Dockerfile và thêm dòng chỉ định nơi tìm image container Ruby. Dự án của tôi dùng Ruby 3.0.0, nên dùng image phù hợp. Nếu bạn dùng phiên bản Ruby khác, không sao. Docker có danh sách tất cả các image được hỗ trợ.
Tiếp theo, chỉ dẫn Docker tạo image Docker:
docker build -t rails_work .
Ở đây, bạn có thể thay rails_work bằng bất kỳ tên nào cho image. Đừng quên dấu chấm ở cuối!
Nếu muốn xem image đã được tạo, liệt kê các image trên hệ thống:
docker image list
Image này chủ yếu rỗng; chưa chứa ứng dụng của chúng ta. Chúng ta có thể chỉ dẫn nó thêm mã từ ứng dụng bằng cách thêm vào cuối Dockerfile:
ADD . /rails_work
WORKDIR /rails_work
RUN bundle install
Điều này sao chép các tệp từ ứng dụng và cài đặt các phụ thuộc. (Thay rails_work bằng tên ứng dụng của bạn.)
Lúc này, chạy lại lệnh tạo image:
docker image list
Có khả năng xảy ra sự cố ở đây, đặc biệt nếu bạn làm điều này cho ứng dụng production hiện có. Bundler có thể phàn nàn rằng phiên bản Bundler mà image cố gắng sử dụng khác với phiên bản tạo ra file Gemfile.lock. Nếu xảy ra, bạn có hai lựa chọn rõ ràng:
- Thay đổi phiên bản mà image đang sử dụng.
- Xóa hoàn toàn
Gemfile.lock. **Nếu làm vậy, hãy chắc chắn ghim các phiên bản Gem cần thiết ở các phiên bản cụ thể, vì lockfile sẽ được tạo lại hoàn toàn.
Nếu bundle install vẫn thất bại, bạn có thể cần cài đặt thêm trong Dockerfile:
RUN apt-get update && apt-get install -y shared-mime-info
Nếu vẫn gặp vấn đề, có thể bạn đã chọn sai image Ruby cơ sở, nên đáng để bắt đầu điều tra từ đó.
Đây là cơ hội tốt để thiết lập biến môi trường:
ENV RAILS_ENV production
ENV RAILS_SERVE_STATIC_FILES true
Tiếp theo, thêm dòng expose cổng 3000, nơi Rails chạy mặc định:
EXPOSE 3000
Cuối cùng, chỉ dẫn container mở bash shell khi khởi động:
CMD ["bash"]
Tóm lại, Dockerfile của bạn trông như sau (thay tên rails_work):
FROM ruby:3.0.0
ADD . /rails_work
WORKDIR /rails_work
RUN bundle install
ENV RAILS_ENV production
ENV RAILS_SERVE_STATIC_FILES true
EXPOSE 3000
CMD ["bash"]
Giải thích các lệnh Docker
Việc hiểu đầy đủ một số lệnh Dockerfile phổ biến sẽ rất hữu ích.
- FROM → Xác định image cơ sở.
- RUN → Thực thi lệnh bên trong container.
- ENV → Định nghĩa biến môi trường.
- WORKDIR → Thay đổi thư mục làm việc của container.
- CMD → Chỉ định chương trình chạy khi container khởi động.
Docker Compose
Theo tài liệu Docker, "Compose" là công cụ để tạo (và khởi động) ứng dụng với nhiều container Docker. Mọi thứ cần thiết để khởi động các container cần thiết của ứng dụng được mô tả trong YAML. Khi ai đó chạy docker-compose up, các container được tạo! Docker-compose cho phép chúng ta mô tả khai báo cấu hình container.
Trước khi tạo file Docker Compose, quan trọng là chỉ ra cho Docker những tệp nên loại trừ khỏi image được xây dựng. Tạo file tên .dockerignore. (Chú ý dấu chấm!) Trong file này, dán nội dung sau:
.git
.dockerignore
.env
Nếu Gemfile được duy trì bởi quy trình xây dựng, hãy thêm Gemfile.lock vào danh sách bỏ qua trên.
Tiếp theo, tạo file docker-compose.yml. Đây là nơi chúng ta mô tả cấu hình container. Chúng ta bắt đầu với nội dung sau:
version: '3.8'
services:
db:
image: postgres
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
volumes:
- postgres:/var/lib/postgresql/data
web:
build: .
command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
volumes:
- .:/Rails-Docker
ports:
- "3000:3000"
depends_on:
- db
volumes:
postgres:
File này tạo hai service: db và web. Container db sẽ được xây dựng từ image postgres có sẵn, và bạn nên thay thế các giá trị liên quan cho POSTGRES_USER và POSTGRES_PASSWORD. Bạn nên cẩn thận không đưa bí mật production vào file này - xem phần "Quản lý bí mật" bên dưới để biết thêm.
Container web được xây dựng từ Dockerfile của chúng ta và khởi động Rails server trên cổng 3000 tại địa chỉ IP 0.0.0.0. Cổng nội bộ 3000 sau đó được ánh xạ đến cổng thực 3000.
Và cuối cùng, chúng ta có volume Postgres để lưu trữ dữ liệu.
Quản lý bí mật
Xác thực tại thời điểm xây dựng có thể là vấn đề cho ứng dụng production. Có thể ứng dụng tìm Gem từ kho riêng, hoặc bạn chỉ cần lưu trữ thông tin đăng nhập database.
Mọi thông tin trực tiếp trong Dockerfile sẽ được nạp vĩnh viễn vào image container, và đây là một lỗ hổng bảo mật phổ biến.
Nếu bạn dùng trình quản lý credentials của Rails, việc cấp quyền truy cập cho Docker (hoặc bất kỳ host nào) khá đơn giản. Trong file Docker Compose, bạn chỉ cần cung cấp biến môi trường RAILS_MASTER_KEY. Cho target compose đã cho, bạn chỉ định key dưới header environment, cần tạo nếu chưa có. File docker-compose ở trên sẽ trở thành:
version: '3.8'
services:
db:
image: postgres
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
volumes:
- postgres:/var/lib/postgresql/data
web:
build: .
command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
volumes:
- .:/Rails-Docker
ports:
- "3000:3000"
depends_on:
- db
environment:
- RAILS_MASTER_KEY=this_would_be_the_key
volumes:
postgres:
Điều này đưa bạn đến ngã rẽ. Bạn có thể muốn commit file này vào kiểm soát nguồn, nhưng bạn chắc chắn không muốn master key hay mật khẩu database được theo dõi bởi kiểm soát nguồn, vì đó sẽ là vấn đề bảo mật nguy hiểm khác. Giải pháp tốt nhất cho đến nay là dùng gem dotenv để truy cập credentials bằng proxy, lưu trữ chúng trong file riêng không được theo dõi bởi kiểm soát nguồn.
Chạy ứng dụng đã Docker hóa
Cuối cùng, bạn có thể chạy ứng dụng đã Docker hóa bằng lệnh:
docker compose up
Tin hay không, chỉ có thế thôi! Docker-compose làm cho việc khởi động container dễ dàng, đặc biệt khi nói đến các đối số dòng lệnh.
Nếu muốn danh sách container đang chạy, chỉ cần chạy:
docker ps
Nếu tên container Rails của bạn là web, bạn có thể thực thi lệnh trên nó một cách trực tiếp. Ví dụ, nếu muốn chạy Rails console, bạn chỉ cần:
docker exec -it web rails console
Nếu chỉ muốn bash shell bên trong container, chạy:
docker exec -it web bash
Một số cạm bẫy khác
Một vấn đề phổ biến với ứng dụng Rails đã Docker hóa trong production là xử lý log. Chúng không nên nằm trong hệ thống container lâu dài. Docker đề xuất log đơn giản được chuyển hướng đến STDOUT. Điều này có thể cấu hình rõ ràng trong config/application.rb.
Vấn đề phổ biến khác là mailer. Nếu ứng dụng dùng mailer, bạn phải định nghĩa rõ ràng các thiết lập kết nối. SMTP là phương thức giao hàng hoàn hảo và thường hoạt động tốt với mặc định, nhưng chúng ta phải cẩn thận đặt vị trí server và các thiết lập khác cho phù hợp với cấu hình container.
Nếu bạn có worker hoặc background job như sidekiq, bạn phải chạy nó trong container riêng.