Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,5 @@ coverage

/app/assets/builds/*
!/app/assets/builds/.keep

/config/credentials/production.key
73 changes: 73 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# syntax=docker/dockerfile:1
# check=error=true

# Match Ruby version from .tool-versions
ARG RUBY_VERSION=3.3.8
FROM ruby:$RUBY_VERSION-slim AS base

WORKDIR /rails

# Install base packages
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y \
curl \
libjemalloc2 \
sqlite3 && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives

# Set production environment
ENV RAILS_ENV="production" \
BUNDLE_DEPLOYMENT="1" \
BUNDLE_PATH="/usr/local/bundle" \
BUNDLE_WITHOUT="development:test"


# Build stage
FROM base AS build

# Install packages needed to build gems
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y \
build-essential \
libffi-dev \
libyaml-dev \
libsqlite3-dev \
git && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives

# Install gems
COPY Gemfile Gemfile.lock ./
RUN bundle install && \
rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
bundle exec bootsnap precompile --gemfile

# Copy application code
COPY . .

# Precompile bootsnap code for faster boot times
RUN bundle exec bootsnap precompile app/ lib/

# Precompile assets for production
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile


# Final production image
FROM base

# Copy built artifacts: gems, application
COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
COPY --from=build /rails /rails

# Run as non-root user
RUN groupadd --system --gid 1000 rails && \
useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \
mkdir -p data && \
chown -R 1000:1000 db log storage tmp data
USER 1000:1000

# Entrypoint prepares the database
ENTRYPOINT ["/rails/bin/docker-entrypoint"]

# Start server on port 3000
EXPOSE 3000
CMD ["./bin/rails", "server", "-b", "0.0.0.0"]
7 changes: 1 addition & 6 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ gem 'redis', '~> 5.0'
gem 'solid_queue', '~> 1.1'
gem 'sprockets-rails'
gem 'stimulus-rails', '~> 1.3'
gem 'sqlite3'
gem 'turbo-rails', '~> 1.5'

# Use Active Storage variant
Expand All @@ -35,8 +36,6 @@ gem 'net-pop', require: false
gem 'net-smtp', require: false

group :development, :test do
# Use sqlite3 as the database for Active Record
gem 'sqlite3'
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug', platforms: %i[mri mingw x64_mingw]
gem 'htmlbeautifier'
Expand Down Expand Up @@ -66,9 +65,5 @@ group :test do
gem 'simplecov', require: false
end

group :production do
# gem 'pg', '~> 1.5'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby]
34 changes: 32 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,35 @@ You can easily set up and run this project in a [VS Code Devcontainer](https://c
- The app will be available at [http://localhost:8080](http://localhost:8080) on your host machine.

## Up and Running
* the devcontainer.json configuration will also copy your ssh settings for github to the docker container so you can
use git without needing to reauthenticate
* the devcontainer.json configuration will also copy your ssh settings for github to the docker container so you can
use git without needing to reauthenticate

## Running with Docker

### Build the image

```sh
bin/docker-build
```

Or pass a custom tag:

```sh
bin/docker-build v1.0.0
```

This tags the image as both `draft:<tag>` and `draft:<git-sha>`.

### Run the container

```sh
bin/docker-run
```

The app will be available at [http://draft.localhost](http://draft.localhost). To use a different port:

```sh
PORT=3001 bin/docker-run
```

Named volumes (`draft-data` and `draft-storage`) persist the database and uploaded files across container restarts.
6 changes: 5 additions & 1 deletion app/models/concerns/mailing_list.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ module MailingList
MAILER_LITE_GROUP = 111712173989824242

def subscribe_to_mailing
MailerLite.create_group_subscriber(MAILER_LITE_GROUP, { email: email }) if Rails.env.production?
return unless Rails.env.production?

MailerLite.create_group_subscriber(MAILER_LITE_GROUP, { email: email })
rescue StandardError => e
Rails.logger.warn("MailerLite subscription failed for #{email}: #{e.message}")
end

end
23 changes: 23 additions & 0 deletions bin/docker-build
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -euo pipefail

IMAGE_NAME="draft"
TAG="${1:-latest}"

# Use git SHA as additional tag when available
GIT_SHA=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")

echo "Building ${IMAGE_NAME}:${TAG} (git: ${GIT_SHA})..."

docker build \
-t "${IMAGE_NAME}:${TAG}" \
-t "${IMAGE_NAME}:${GIT_SHA}" \
.

echo ""
echo "Built images:"
echo " ${IMAGE_NAME}:${TAG}"
echo " ${IMAGE_NAME}:${GIT_SHA}"
echo ""
echo "Run with:"
echo " docker run -p 3000:3000 ${IMAGE_NAME}:${TAG}"
2 changes: 1 addition & 1 deletion bin/docker-entrypoint
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ if [ -z "${LD_PRELOAD+x}" ]; then
fi

# If running the rails server then create or migrate existing database
if [ "${@: -2:1}" == "./bin/rails" ] && [ "${@: -1:1}" == "server" ]; then
if [ "$1" == "./bin/rails" ] && [ "$2" == "server" ]; then
./bin/rails db:prepare
fi

Expand Down
32 changes: 32 additions & 0 deletions bin/docker-run
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
set -euo pipefail

IMAGE_NAME="draft"
TAG="${1:-latest}"
CONTAINER_NAME="draft"
PORT="${PORT:-80}"

# Remove existing container if present
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
echo "Removing existing '${CONTAINER_NAME}' container..."
docker rm -f "$CONTAINER_NAME"
fi

echo "Starting ${IMAGE_NAME}:${TAG} on port ${PORT}..."

docker run -d \
--name "$CONTAINER_NAME" \
-p "${PORT}:3000" \
-e RAILS_ASSUME_SSL=false \
-e RAILS_FORCE_SSL=false \
-e RAILS_MASTER_KEY="${RAILS_MASTER_KEY:-$(cat config/credentials/production.key)}" \
-e DATABASE_URL="sqlite3:/rails/data/production.sqlite3" \
-v draft-data:/rails/data \
-v draft-storage:/rails/storage \
"${IMAGE_NAME}:${TAG}"

if [ "$PORT" = "80" ]; then
echo "Running at http://draft.localhost"
else
echo "Running at http://draft.localhost:${PORT}"
fi
1 change: 1 addition & 0 deletions config/credentials/production.yml.enc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
GIPj3kbP/xHIbHWy5G6bSAR0PMzVe30hJsRgdxOvjcTIQEuVPO8uEhyvTVANlO/1RzDVVnKJtxnZRO0ty/Z1N/KYLB6VhmKQcKr0+g8bY/EneEcfyRj3iarEg1MHf860IOA4aF5snBIQw4Qs229jFWwVZ7h+CDkIhoVeBXz75buXu23VXZmn26q4iv8sxlehAqDfUyb8po2CWf0sXFLg2N5T/Mk6hrDDMb9rJYu3Ut3VlGom5/LFAhCjTuxIudUf6LMjoM5pIKJvi66vU9vwBBQzbASxgMxYMaaTaJWgbkyvgvs9hWY3NvXk0t6dIbdsRKqQNT+Ou2c6cbscRFhJLL/AfRlvceiwwHItwakrhgn79z2i4X8/I7mTu8OyYQ6pgOgfXQRNmcnV4Ef+V+lcOFHLl/9C3/lV7+md/CplK1y9nG/k9JID9e2h7f8EaZl74Th3gL5YEC41XnPtpVcU32Y4t09/Ya6HiOpfG2OiEpWawaX2N1gMdxlt--drWRJWyzzwUZdrhF--b/iTIrkCtZAy0iE1tCYB3A==
16 changes: 8 additions & 8 deletions config/environments/production.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@
config.active_storage.service = :local

# Assume all access to the app is happening through a SSL-terminating reverse proxy.
config.assume_ssl = true
config.assume_ssl = ENV.fetch('RAILS_ASSUME_SSL', 'true') == 'true'

# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
config.force_ssl = true
config.force_ssl = ENV.fetch('RAILS_FORCE_SSL', 'true') == 'true'

# Skip http-to-https redirect for the default health check endpoint.
# config.ssl_options = { redirect: { exclude: ->(request) { request.path == "/up" } } }
Expand Down Expand Up @@ -62,12 +62,12 @@

# Specify outgoing SMTP server. Remember to add smtp/* credentials via rails credentials:edit.
config.action_mailer.smtp_settings = {
user_name: Rails.application.credentials.mailer[:username],
password: Rails.application.credentials.mailer[:password],
address: Rails.application.credentials.mailer[:address],
domain: Rails.application.credentials.mailer[:domain],
port: Rails.application.credentials.mailer[:port],
authentication: Rails.application.credentials.mailer[:authentication],
user_name: Rails.application.credentials.dig(:mailer,:username),
password: Rails.application.credentials.dig(:mailer,:password),
address: Rails.application.credentials.dig(:mailer,:address),
domain: Rails.application.credentials.dig(:mailer,:domain),
port: Rails.application.credentials.dig(:mailer,:port),
authentication: Rails.application.credentials.dig(:mailer,:authentication),
enable_starttls_auto: true
}

Expand Down
2 changes: 1 addition & 1 deletion config/initializers/mailerlite.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
MailerLite.configure do |config|
config.api_key = Rails.application.credentials.mailerlite[:api_key] if Rails.env.production?
config.api_key = Rails.application.credentials.dig(:mailerlite,:api_key) if Rails.env.production?
# config.timeout = 10
end
Loading