From b108e21e195bacd3db5fee08986540ab47bd67f9 Mon Sep 17 00:00:00 2001 From: Sean Dickinson Date: Mon, 12 Oct 2026 20:19:14 -0400 Subject: [PATCH 1/4] feat: add staging environment --- config/cable.yml | 5 ++++- config/cache.yml | 4 ++++ config/database.yml | 18 ++++++++++++++++++ config/environments/production.rb | 4 ++-- config/environments/staging.rb | 8 ++++++++ config/queue.yml | 3 +++ config/recurring.yml | 5 ++++- 7 files changed, 43 insertions(+), 4 deletions(-) create mode 100644 config/environments/staging.rb diff --git a/config/cable.yml b/config/cable.yml index b9adc5a..74d2b53 100644 --- a/config/cable.yml +++ b/config/cable.yml @@ -8,10 +8,13 @@ development: test: adapter: test -production: +production: &solid_cable adapter: solid_cable connects_to: database: writing: cable polling_interval: 0.1.seconds message_retention: 1.day + +staging: + <<: *solid_cable diff --git a/config/cache.yml b/config/cache.yml index 19d4908..344c2cc 100644 --- a/config/cache.yml +++ b/config/cache.yml @@ -14,3 +14,7 @@ test: production: database: cache <<: *default + +staging: + database: cache + <<: *default diff --git a/config/database.yml b/config/database.yml index 302d638..51daad3 100644 --- a/config/database.yml +++ b/config/database.yml @@ -38,3 +38,21 @@ production: <<: *default database: storage/production_cable.sqlite3 migrations_paths: db/cable_migrate + +# Staging uses its own database files in the same storage/ volume as production. +staging: + primary: + <<: *default + database: storage/staging.sqlite3 + cache: + <<: *default + database: storage/staging_cache.sqlite3 + migrations_paths: db/cache_migrate + queue: + <<: *default + database: storage/staging_queue.sqlite3 + migrations_paths: db/queue_migrate + cable: + <<: *default + database: storage/staging_cable.sqlite3 + migrations_paths: db/cable_migrate diff --git a/config/environments/production.rb b/config/environments/production.rb index bed8ce3..fd34108 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -58,7 +58,7 @@ # config.action_mailer.raise_delivery_errors = false # Set host to be used by links generated in mailer templates. - config.action_mailer.default_url_options = { host: "staging-classroom.endsideout.org", protocol: "https" } + config.action_mailer.default_url_options = { host: "classroom.endsideout.org", protocol: "https" } # Specify outgoing SMTP server. Remember to add smtp/* credentials via bin/rails credentials:edit. # config.action_mailer.smtp_settings = { @@ -81,7 +81,7 @@ # Enable DNS rebinding protection and other `Host` header attacks. config.hosts = [ - "staging-classroom.endsideout.org" + "classroom.endsideout.org" ] # Skip DNS rebinding protection for the default health check endpoint. diff --git a/config/environments/staging.rb b/config/environments/staging.rb new file mode 100644 index 0000000..3980b85 --- /dev/null +++ b/config/environments/staging.rb @@ -0,0 +1,8 @@ +require_relative "production" + +Rails.application.configure do + staging_host = "staging-classroom.endsideout.org" + + config.action_mailer.default_url_options = { host: staging_host, protocol: "https" } + config.hosts = [ staging_host ] +end diff --git a/config/queue.yml b/config/queue.yml index 9eace59..8d1d6b7 100644 --- a/config/queue.yml +++ b/config/queue.yml @@ -16,3 +16,6 @@ test: production: <<: *default + +staging: + <<: *default diff --git a/config/recurring.yml b/config/recurring.yml index b4207f9..3a12138 100644 --- a/config/recurring.yml +++ b/config/recurring.yml @@ -9,7 +9,10 @@ # priority: 2 # schedule: at 5am every day -production: +production: &production clear_solid_queue_finished_jobs: command: "SolidQueue::Job.clear_finished_in_batches(sleep_between_batches: 0.3)" schedule: every hour at minute 12 + +staging: + <<: *production From e6f51b8db1c0f10a91bda97aa8aa6bc42eabadfc Mon Sep 17 00:00:00 2001 From: Sean Dickinson Date: Mon, 12 Oct 2026 20:25:37 -0400 Subject: [PATCH 2/4] feat: add staging credentials --- config/credentials/staging.yml.enc | 1 + 1 file changed, 1 insertion(+) create mode 100644 config/credentials/staging.yml.enc diff --git a/config/credentials/staging.yml.enc b/config/credentials/staging.yml.enc new file mode 100644 index 0000000..1779ce1 --- /dev/null +++ b/config/credentials/staging.yml.enc @@ -0,0 +1 @@ +eeoCByo6ZKfHPIAiUs+RWlMG6ctONoh7PtOa+YhVOHmJ+Cyx/g8veArB8vviyXaXJKDSnTVKGtmN4iFkRmv8U80LVXuIrguyLPU71VhTVjN6D/Z3msO8lE4EuFVqmm2nSgwxqv3kNsnzq0PvUaFka/gLMGYiJc1lxnqNaa49uLPPoGO6ou8HwarmXNfUsiga7P6NIgltzccC6kEqxbfriBophSo4vzLdYCo1jfkb01IFa1TALIB3r6HZ68dFXLiKaXdjEu0f1u0gL0wOajGbl0ov8MEhGMr0sQUV/BjjEy65JNSYU/95OCoZxbljVgYpSX+DYuCrEdSDqOVL6d/icFGSrQZ3SnRWHtE4xi/38m4J77WYwO8m8VE1tcWCOgLNfXKQcy+lQIBWOThypclncYLTuCXZYGucKwd6GtRpL2pWcskP0vKwNSXtDauLp/mdOCiOA3JXr8rkxsEiwKtH9PlBHDQnSczjW+x+aFk5M36/7JkPLgNKQXfJ--1UqKYQAZ+lgiu89x--T/GJd6zjnSiOqNo1NLjbsw== \ No newline at end of file From d3d720f81a61560386da70af4340863a27040918 Mon Sep 17 00:00:00 2001 From: Sean Dickinson Date: Mon, 12 Oct 2026 20:29:49 -0400 Subject: [PATCH 3/4] feat: restructure kamal for staging --- .kamal/{secrets => secrets-common} | 1 - .kamal/secrets.production | 3 +++ .kamal/secrets.staging | 3 +++ config/deploy.staging.yml | 14 ++++++++++++++ config/deploy.yml | 24 ++++++++++++------------ 5 files changed, 32 insertions(+), 13 deletions(-) rename .kamal/{secrets => secrets-common} (95%) create mode 100644 .kamal/secrets.production create mode 100644 .kamal/secrets.staging create mode 100644 config/deploy.staging.yml diff --git a/.kamal/secrets b/.kamal/secrets-common similarity index 95% rename from .kamal/secrets rename to .kamal/secrets-common index 9d28e1e..728b94c 100644 --- a/.kamal/secrets +++ b/.kamal/secrets-common @@ -17,6 +17,5 @@ # KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD # Improve security by using a password manager. Never check config/master.key into git! -RAILS_MASTER_KEY=$(cat config/credentials/production.key) KAMAL_REGISTRY_PASSWORD=$(aws ecr get-login-password --region us-east-2) diff --git a/.kamal/secrets.production b/.kamal/secrets.production new file mode 100644 index 0000000..78c9ae1 --- /dev/null +++ b/.kamal/secrets.production @@ -0,0 +1,3 @@ +# Secrets for `kamal -d production`. Shared secrets live in .kamal/secrets-common. +# DO NOT ENTER RAW CREDENTIALS HERE! This file needs to be safe for git. +RAILS_MASTER_KEY=$(cat config/credentials/production.key) diff --git a/.kamal/secrets.staging b/.kamal/secrets.staging new file mode 100644 index 0000000..b7c5f7e --- /dev/null +++ b/.kamal/secrets.staging @@ -0,0 +1,3 @@ +# Secrets for `kamal -d staging`. Shared secrets live in .kamal/secrets-common. +# DO NOT ENTER RAW CREDENTIALS HERE! This file needs to be safe for git. +RAILS_MASTER_KEY=$(cat config/credentials/staging.key) diff --git a/config/deploy.staging.yml b/config/deploy.staging.yml new file mode 100644 index 0000000..d83beaa --- /dev/null +++ b/config/deploy.staging.yml @@ -0,0 +1,14 @@ +# Staging destination. Deploy with: bin/kamal deploy -d staging +# Values here are merged over config/deploy.yml. + +servers: + web: + - 18.117.181.111 + +ssh: + user: ec2-user + keys: ["~/.ssh/2026rfg-staging.pem"] + +env: + clear: + RAILS_ENV: staging diff --git a/config/deploy.yml b/config/deploy.yml index d4a5554..ff89f6d 100644 --- a/config/deploy.yml +++ b/config/deploy.yml @@ -4,14 +4,17 @@ service: endsideout # Name of the container image (use your-user/app-name on external registries). image: endsideout -# Deploy to these servers. -servers: - web: - - 18.117.181.111 - # job: - # hosts: - # - 192.168.0.1 - # cmd: bin/jobs +# Always deploy to a named destination (config/deploy..yml), for example `bin/kamal deploy -d staging`. +require_destination: true + +# Servers are set per destination. +# servers: +# web: +# - 192.168.0.1 +# job: +# hosts: +# - 192.168.0.1 +# cmd: bin/jobs # Enable SSL auto certification via Let's Encrypt and allow for multiple apps on a single web server. # If used with Cloudflare, set encryption mode in SSL/TLS setting to "Full" to enable CF-to-app encryption. @@ -97,10 +100,7 @@ builder: # - GITHUB_TOKEN - RAILS_MASTER_KEY -# Use a different ssh user than root -ssh: - user: ec2-user - keys: ["~/.ssh/2026rfg-staging.pem"] +# The ssh user and keys are set per destination. # Use accessory services (secrets come from .kamal/secrets). From 47878a2489c067d0a568b13b4b40b0f2c8dbe482 Mon Sep 17 00:00:00 2001 From: Sean Dickinson Date: Wed, 14 Oct 2026 20:35:13 -0400 Subject: [PATCH 4/4] docs: add documentation --- docs/deployment.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 docs/deployment.md diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100644 index 0000000..03213dd --- /dev/null +++ b/docs/deployment.md @@ -0,0 +1,29 @@ +# Deployment + +## Configuring AWS Instance +Kamal's deployment model includes a proxy that runs on the instance and handles the app via a blue/green deployment model. +During deployment the current version of the app is running while the new version is being deployed. +During the deployment process, the proxy runs status checks on the new version of the app to ensure it is healthy before switching traffic over to it. +This approach means that during deployment, two versions of the app are running at the same time, which can cause memory issues if the instance is too small. +When choosing an instance you either need to ensure the instance has enough memory to run two versions of the app at the same time, or you need to add swap space to the instance to ensure it doesn't run out of memory during deployment. +If you choose to add swap space, you need to ensure the instance has a big enough volume to add swap space. +*AWS defaults to 8GB volumes, which is realistically not enough to add swap space (or honestly to not run into other issues).* Be sure that you choose a volume size that is big enough to add swap space. 20 GB is a good minimum. + +## Secrets and Credentials +### Rails Credentials +The encryption keys for Rails credentials are stored in AWS Secrets Manager. +In order to deploy you need to create a key file in the config/credentials folder such as `config/credentials/staging.key` with the value from secrets manager. +*These files should remain gitignored and not be checked into source control.* + +### SSH Keys +Kamal uses SSH to connect to the instance and deploy the application. This key is stored in AWS Secrets Manager and needs to be downloaded and placed in the `~/.ssh/` folder with permissions set to 600. +Note you should use the same file name referenced for the key in the environment specific deployment configuration. For example [deploy.staging.yml](../config/deploy.staging.yml) + +## AWS CLI +You need to be logged into the AWS CLI to deploy. You can use `aws configure` to set up your credentials. +You can make a named profile for this AWS account if you have multiple AWS accounts using `aws configure --profile your_profile_name`. +To ensure that kamal uses the correct profile, run EXPORT AWS_PROFILE=your_profile_name to set the profile for the current terminal session. + +## Important Gotchas +- We have configured kamal to use explicit deploy targets. Therefore you always need to specify the target environment when using any kamal command. For example, `bin/kamal deploy -d staging` +- Kamal only deploys committed code. If you have uncommitted changes, they will not be deployed. \ No newline at end of file