Skip to content

Commit bbf15f5

Browse files
committed
Support for Kamal deployment; plus credentials.yml.tt
1 parent eeaec80 commit bbf15f5

File tree

8 files changed

+225
-13
lines changed

8 files changed

+225
-13
lines changed

.kamal/secrets

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Secrets defined here are available for reference under registry/password, env/secret, builder/secrets,
2+
# and accessories/*/env/secret in config/deploy.yml. All secrets should be pulled from either
3+
# password manager, ENV, or a file. DO NOT ENTER RAW CREDENTIALS HERE! This file needs to be safe for git.
4+
5+
# Grab the registry password from ENV
6+
KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD
7+
8+
# Improve security by using a password manager. Never check config/master.key or config/credentials/*.key into git!
9+
RAILS_MASTER_KEY=$(cat config/credentials/production.key)
10+
11+
# Either use .env or rails credentials to store database password.
12+
# HOSTEDGPT_DATABASE_PASSWORD=$HOSTEDGPT_DATABASE_PASSWORD
13+
credentials=$(bin/rails credentials:show --environment production)
14+
HOSTEDGPT_DATABASE_PASSWORD=$(echo "$credentials" | yq '.database.password // "password"')
15+
16+
# Used by postgres:16 image to set password
17+
POSTGRES_PASSWORD=$HOSTEDGPT_DATABASE_PASSWORD

Gemfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ group :development do
7575
gem "rubocop-capybara"
7676
gem "rubocop-minitest"
7777
gem "dockerfile-rails", ">= 1.6"
78+
79+
gem "kamal", "~> 2.0"
7880
end
7981

8082
group :test do

Gemfile.lock

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ GEM
106106
aws-eventstream (~> 1, >= 1.0.2)
107107
base64 (0.2.0)
108108
bcrypt (3.1.20)
109+
bcrypt_pbkdf (1.1.1)
110+
bcrypt_pbkdf (1.1.1-arm64-darwin)
111+
bcrypt_pbkdf (1.1.1-x86_64-darwin)
109112
bigdecimal (3.1.7)
110113
bindex (0.8.1)
111114
bootsnap (1.17.0)
@@ -131,7 +134,9 @@ GEM
131134
reline (>= 0.3.1)
132135
dockerfile-rails (1.6.10)
133136
rails (>= 3.0.0)
137+
dotenv (3.1.4)
134138
drb (2.2.1)
139+
ed25519 (1.3.0)
135140
erubi (1.12.0)
136141
event_stream_parser (1.0.0)
137142
faraday (2.8.1)
@@ -142,6 +147,10 @@ GEM
142147
multipart-post (~> 2)
143148
faraday-net_http (3.0.2)
144149
ffi (1.17.0)
150+
ffi (1.17.0-aarch64-linux-musl)
151+
ffi (1.17.0-arm64-darwin)
152+
ffi (1.17.0-x86_64-darwin)
153+
ffi (1.17.0-x86_64-linux-gnu)
145154
globalid (1.2.1)
146155
activesupport (>= 6.1)
147156
hashie (5.0.0)
@@ -162,6 +171,17 @@ GEM
162171
json (2.7.1)
163172
jwt (2.8.1)
164173
base64
174+
kamal (2.2.2)
175+
activesupport (>= 7.0)
176+
base64 (~> 0.2)
177+
bcrypt_pbkdf (~> 1.0)
178+
concurrent-ruby (~> 1.2)
179+
dotenv (~> 3.1)
180+
ed25519 (~> 1.2)
181+
net-ssh (~> 7.0)
182+
sshkit (>= 1.23.0, < 2.0)
183+
thor (~> 1.3)
184+
zeitwerk (~> 2.5)
165185
language_server-protocol (3.17.0.3)
166186
lint_roller (1.1.0)
167187
logger (1.6.1)
@@ -196,8 +216,13 @@ GEM
196216
net-protocol
197217
net-protocol (0.2.2)
198218
timeout
219+
net-scp (4.0.0)
220+
net-ssh (>= 2.6.5, < 8.0.0)
221+
net-sftp (4.0.0)
222+
net-ssh (>= 5.0.0, < 8.0.0)
199223
net-smtp (0.4.0.1)
200224
net-protocol
225+
net-ssh (7.3.0)
201226
nio4r (2.7.0)
202227
nokogiri (1.16.3-aarch64-linux)
203228
racc (~> 1.4)
@@ -229,6 +254,7 @@ GEM
229254
omniauth-rails_csrf_protection (1.0.2)
230255
actionpack (>= 4.2)
231256
omniauth (~> 2.0)
257+
ostruct (0.6.0)
232258
parallel (1.24.0)
233259
parser (3.2.2.4)
234260
ast (~> 2.4.1)
@@ -378,6 +404,12 @@ GEM
378404
actionpack (>= 5.2)
379405
activesupport (>= 5.2)
380406
sprockets (>= 3.0.0)
407+
sshkit (1.23.1)
408+
base64
409+
net-scp (>= 1.1.2)
410+
net-sftp (>= 2.1.2)
411+
net-ssh (>= 2.8.0)
412+
ostruct
381413
standard (1.32.1)
382414
language_server-protocol (~> 3.17.0.2)
383415
lint_roller (~> 1.0)
@@ -455,6 +487,7 @@ DEPENDENCIES
455487
dockerfile-rails (>= 1.6)
456488
image_processing (~> 1.13.0)
457489
importmap-rails
490+
kamal (~> 2.0)
458491
minitest-stub_any_instance
459492
name_of_person
460493
omniauth (~> 2.1)

README.md

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,17 @@ This project is led by an experienced rails developer, but I'm actively looking
2727
- [Troubleshooting Render](#troubleshooting-render)
2828
- [Deploy the app on Fly.io](#deploy-the-app-on-flyio)
2929
- [Deploy the app on Heroku](#deploy-the-app-on-heroku)
30+
- [Deploy to own servers with Kamal2](#deploy-to-own-servers-with-kamal2)
3031
- [Deploy on your own server](#deploy-on-your-own-server)
3132
- [Configure optional features](#configure-optional-features)
32-
- [Give assistant access to your Google apps](#configuring-google-tools)
33+
- [Configuring Google Tools](#configuring-google-tools)
3334
- [Authentication](#authentication)
3435
- [Password authentication](#password-authentication)
3536
- [Google OAuth authentication](#google-oauth-authentication)
3637
- [HTTP header authentication](#http-header-authentication)
3738
- [Contribute as a developer](#contribute-as-a-developer)
38-
- [Running locally](#Running-locally)
39-
- [Alternatively, you can skip Docker:](#alternatively-you-can-set-skip-docker)
39+
- [Running locally](#running-locally)
40+
- [Alternatively, you can skip Docker](#alternatively-you-can-skip-docker)
4041
- [Running tests](#running-tests)
4142
- [Understanding the Docker configuration](#understanding-the-docker-configuration)
4243
- [Changelog](#changelog)
@@ -111,6 +112,45 @@ Eligible students can apply for Heroku platform credits through [Heroku for GitH
111112

112113
You may want to read about [configuring optional features](#configure-optional-features).
113114

115+
## Deploy to own servers with Kamal
116+
117+
[Kamal](https://kamal-deploy.org/) offers zero-downtime deploys, rolling restarts, asset bridging, remote builds, accessory service management, and everything else you need to deploy and manage your web app in production with Docker. Originally built for Rails apps, Kamal will work with any type of web app that can be containerized.
118+
119+
First, create your production credentials file.
120+
121+
```plain
122+
bin/rails credentials:edit --environment production
123+
```
124+
125+
Next, uncomment the `database:` section
126+
127+
```yaml
128+
database:
129+
password: some-long-string
130+
```
131+
132+
Second, create a Docker Hub access token and store it as local env var `KAMAL_REGISTRY_PASSWORD`.
133+
134+
Next, edit `config/deploy.yml`:
135+
136+
1. Change `my-docker-user` to your Docker Hub username
137+
2. Change `168.192.0.1` to the IP or hostname of your target Linux server
138+
3. If you need to `ssh` into that server as anything other than `root` user, then uncomment `ssh:` section and edit your ssh username.
139+
4. Change `hostedgpt.example.com` to the public CNAME or A record that points to your server IP address.
140+
141+
Next, commit all the changes to git so Kamal picks them up.
142+
143+
```plain
144+
git add .
145+
git commit -m "Add production credentials and Kamal config"
146+
```
147+
148+
Now, run the command to setup the Postgres database, build HostedGPT using docker buildx, and deploy it to your server:
149+
150+
```plain
151+
kamal setup
152+
```
153+
114154
## Deploy on your own server
115155

116156
There are only two services that need to be running for this app to work: the Puma web server and a Postgres database.

config/database.yml

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,27 @@
11
default: &default
22
adapter: postgresql
33
encoding: unicode
4-
host: localhost
54
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
6-
port: <%= ENV['HOSTEDGPT_DATABASE_PORT'] || 5432 %>
7-
<% if RUBY_PLATFORM =~ /darwin/ %>
5+
<% if ENV["HOSTEDGPT_DATABASE_HOST"] %>
6+
host: <%= ENV["HOSTEDGPT_DATABASE_HOST"] %>
7+
<% end %>
8+
<% if ENV["HOSTEDGPT_DATABASE_PORT"] %>
9+
port: <%= ENV["HOSTEDGPT_DATABASE_PORT"] %>
10+
<% end %>
11+
<% if RUBY_PLATFORM =~ /darwin/ %>
812
gssencmode: disable
9-
<% end %>
13+
<% end %>
1014

1115
development:
1216
<<: *default
13-
database: <%= ENV['HOSTEDGPT_DEV_DB'] || "hostedgpt_development" %>
17+
database: <%= ENV.fetch("HOSTEDGPT_DEV_DB", "hostedgpt_development") %>
1418

1519
test:
1620
<<: *default
17-
database: <%= ENV['HOSTEDGPT_TEST_DB'] || "hostedgpt_test" %>
21+
database: <%= ENV.fetch("HOSTEDGPT_TEST_DB", "hostedgpt_test") %>
1822

1923
production:
2024
<<: *default
21-
database: hostedgpt_production
22-
username: hostedgpt
25+
database: <%= ENV.fetch("HOSTEDGPT_PRODUCTION_DB", "hostedgpt_production") %>
26+
username: <%= ENV.fetch("HOSTEDGPT_DATABASE_USERNAME", "hostedgpt") %>
2327
password: <%= ENV["HOSTEDGPT_DATABASE_PASSWORD"] %>
24-

config/deploy.yml

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Name of your application. Used to uniquely configure containers.
2+
service: hostedgpt
3+
4+
# Name of the container image.
5+
image: my-docker-user/hostedgpt
6+
7+
# Deploy to these servers.
8+
servers:
9+
web:
10+
- 168.192.0.1
11+
# job:
12+
# hosts:
13+
# - 168.192.0.1
14+
# cmd: bin/rake solid_queue:start
15+
16+
# Enable SSL auto certification via Let's Encrypt (and allow for multiple apps on one server).
17+
# Set ssl: false if using something like Cloudflare to terminate SSL (but keep host!).
18+
proxy:
19+
ssl: true
20+
host: hostedgpt.example.com
21+
app_port: 8080
22+
23+
# Credentials for your image host.
24+
registry:
25+
# Specify the registry server, if you're not using Docker Hub
26+
# server: registry.digitalocean.com / ghcr.io / ...
27+
username: my-docker-user
28+
29+
# Always use an access token rather than real password when possible.
30+
password:
31+
- KAMAL_REGISTRY_PASSWORD
32+
33+
# Inject ENV variables into containers (secrets come from .kamal/secrets).
34+
env:
35+
secret:
36+
- RAILS_MASTER_KEY
37+
- HOSTEDGPT_DATABASE_PASSWORD
38+
clear:
39+
# Run the Solid Queue Supervisor inside the web server's Puma process to do jobs.
40+
# When you start using multiple servers, you should split out job processing to a dedicated machine.
41+
RUN_SOLID_QUEUE_IN_PUMA: true
42+
43+
# Set number of processes dedicated to Solid Queue (default: 1)
44+
# JOB_CONCURRENCY: 3
45+
46+
# Set number of cores available to the application on each server (default: 1).
47+
# WEB_CONCURRENCY: 2
48+
49+
HOSTEDGPT_FORCE_SSL: "false"
50+
51+
# Match this to any external database server to configure Active Record correctly
52+
HOSTEDGPT_DATABASE_HOST: hostedgpt-db
53+
HOSTEDGPT_DATABASE_USERNAME: postgres
54+
HOSTEDGPT_PRODUCTION_DB: hostedgpt_production
55+
56+
# Log everything from Rails
57+
RAILS_LOG_LEVEL: debug
58+
59+
# Aliases are triggered with "bin/kamal <alias>". You can overwrite arguments on invocation:
60+
# "bin/kamal logs -r job" will tail logs from the first server in the job section.
61+
aliases:
62+
console: app exec --interactive --reuse "bin/rails console"
63+
shell: app exec --interactive --reuse "bash"
64+
logs: app logs -f
65+
dbc: app exec --interactive --reuse "bin/rails dbconsole"
66+
67+
# Use a persistent storage volume for sqlite database files and local Active Storage files.
68+
# Recommended to change this to a mounted volume path that is backed up off server.
69+
volumes:
70+
- "hostedgpt_storage:/rails/storage"
71+
72+
# Bridge fingerprinted assets, like JS and CSS, between versions to avoid
73+
# hitting 404 on in-flight requests. Combines all files from new and old
74+
# version inside the asset_path.
75+
asset_path: /rails/public/assets
76+
77+
# Configure the image builder.
78+
builder:
79+
arch: amd64
80+
81+
# # Build image via remote server (useful for faster amd64 builds on arm64 computers)
82+
# remote: ssh://docker@docker-builder-server
83+
#
84+
# # Pass arguments and secrets to the Docker build process
85+
# args:
86+
# RUBY_VERSION: ruby-3.3.5
87+
# secrets:
88+
# - GITHUB_TOKEN
89+
# - RAILS_MASTER_KEY
90+
91+
# Use a different ssh user than root
92+
# ssh:
93+
# user: deploy
94+
95+
# Use accessory services (secrets come from .kamal/secrets).
96+
accessories:
97+
db:
98+
image: postgres:16
99+
host: 168.192.0.1
100+
# port: 5432
101+
env:
102+
clear:
103+
POSTGRES_DB: hostedgpt_production
104+
secret:
105+
- POSTGRES_PASSWORD
106+
directories:
107+
- data:/var/lib/postgresql/data

config/environments/production.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
# config.assume_ssl = true
4949

5050
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
51-
config.force_ssl = true
51+
config.force_ssl = ENV["HOSTEDGPT_FORCE_SSL"] != "false"
5252

5353
# Log to STDOUT by default
5454
config.logger = ActiveSupport::Logger.new($stdout)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
2+
secret_key_base: <%= secret_key_base %>
3+
4+
active_record_encryption:
5+
primary_key: <%= SecureRandom.alphanumeric(32) %>
6+
deterministic_key: <%= SecureRandom.alphanumeric(32) %>
7+
key_derivation_salt: <%= SecureRandom.alphanumeric(32) %>
8+
9+
# database:
10+
# password: <%= SecureRandom.alphanumeric(32) %>

0 commit comments

Comments
 (0)