Skip to content

Commit 4d386d4

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

File tree

8 files changed

+243
-30
lines changed

8 files changed

+243
-30
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: 61 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,26 +20,28 @@ This project is led by an experienced rails developer, but I'm actively looking
2020

2121
## Table of Contents
2222

23-
- [Top features of HostedGPT](#top-features-of-hostedgpt)
24-
- [Watch a short demo](#watch-a-short-demo)
25-
- [Table of Contents](#table-of-contents)
26-
- [Deploy the app on Render](#deploy-the-app-on-render)
27-
- [Troubleshooting Render](#troubleshooting-render)
28-
- [Deploy the app on Fly.io](#deploy-the-app-on-flyio)
29-
- [Deploy the app on Heroku](#deploy-the-app-on-heroku)
30-
- [Deploy on your own server](#deploy-on-your-own-server)
31-
- [Configure optional features](#configure-optional-features)
32-
- [Give assistant access to your Google apps](#configuring-google-tools)
33-
- [Authentication](#authentication)
34-
- [Password authentication](#password-authentication)
35-
- [Google OAuth authentication](#google-oauth-authentication)
36-
- [HTTP header authentication](#http-header-authentication)
37-
- [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)
40-
- [Running tests](#running-tests)
41-
- [Understanding the Docker configuration](#understanding-the-docker-configuration)
42-
- [Changelog](#changelog)
23+
- [HostedGPT v0.6](#hostedgpt-v06)
24+
- [Top features of HostedGPT](#top-features-of-hostedgpt)
25+
- [Watch a short demo](#watch-a-short-demo)
26+
- [Table of Contents](#table-of-contents)
27+
- [Deploy the app on Render](#deploy-the-app-on-render)
28+
- [Troubleshooting Render](#troubleshooting-render)
29+
- [Deploy the app on Fly.io](#deploy-the-app-on-flyio)
30+
- [Deploy the app on Heroku](#deploy-the-app-on-heroku)
31+
- [Deploy to own servers with Kamal2](#deploy-to-own-servers-with-kamal2)
32+
- [Deploy on your own server](#deploy-on-your-own-server)
33+
- [Configure optional features](#configure-optional-features)
34+
- [Configuring Google Tools](#configuring-google-tools)
35+
- [Authentication](#authentication)
36+
- [Password authentication](#password-authentication)
37+
- [Google OAuth authentication](#google-oauth-authentication)
38+
- [HTTP header authentication](#http-header-authentication)
39+
- [Contribute as a developer](#contribute-as-a-developer)
40+
- [Running locally](#running-locally)
41+
- [Alternatively, you can skip Docker](#alternatively-you-can-skip-docker)
42+
- [Running tests](#running-tests)
43+
- [Understanding the Docker configuration](#understanding-the-docker-configuration)
44+
- [Changelog](#changelog)
4345

4446
## Deploy the app on Render
4547

@@ -111,6 +113,45 @@ Eligible students can apply for Heroku platform credits through [Heroku for GitH
111113

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

116+
## Deploy to own servers with Kamal2
117+
118+
.. intro to Kamal
119+
120+
First, create your production credentials file.
121+
122+
```plain
123+
bin/rails credentials:edit --environment production
124+
```
125+
126+
Next, uncomment the `database:` section
127+
128+
```yaml
129+
database:
130+
password: some-long-string
131+
```
132+
133+
Second, create a Docker Hub access token and store it as local env var `KAMAL_REGISTRY_PASSWORD`.
134+
135+
Next, edit `config/deploy.yml`:
136+
137+
1. Change `my-docker-user` to your Docker Hub username
138+
2. Change `168.192.0.1` to the IP or hostname of your target Linux server
139+
3. If you need to `ssh` into that server as anything other than `root` user, then uncomment `ssh:` section and edit your ssh username.
140+
4. Change `hostedgpt.example.com` to the public CNAME or A record that points to your server IP address.
141+
142+
Next, commit all the changes to git so Kamal picks them up.
143+
144+
```plain
145+
git add .
146+
git commit -m "Add production credentials and Kamal config"
147+
```
148+
149+
Now, run the command to setup the Postgres database, build HostedGPT using docker buildx, and deploy it to your server:
150+
151+
```plain
152+
kamal setup
153+
```
154+
114155
## Deploy on your own server
115156

116157
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)