diff --git a/.github/workflows/build_and_push.yml b/.github/workflows/build_and_push.yml index 1be0cca5..812f071f 100644 --- a/.github/workflows/build_and_push.yml +++ b/.github/workflows/build_and_push.yml @@ -1,13 +1,23 @@ name: Docker image build and push + on: workflow_dispatch: + inputs: + branch: + description: "The branch to build the Docker image from" + required: false + default: "main" release: types: [created] + jobs: build-and-push-docker: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - name: Checkout code + uses: actions/checkout@v2 + with: + ref: ${{ github.event.inputs.branch || github.ref_name }} - name: Set up QEMU uses: docker/setup-qemu-action@v1 - name: Set up Docker Buildx @@ -32,7 +42,7 @@ jobs: context: . file: ./Dockerfile push: true - tags: freikin/dawarich:latest,freikin/dawarich:${{ github.event.release.tag_name }} + tags: freikin/dawarich:latest,freikin/dawarich:${{ github.event.inputs.branch || github.ref_name }} platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6 cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache diff --git a/Dockerfile b/Dockerfile index d809b2d7..ef2b74c5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM ruby:3.3.4-alpine ENV APP_PATH=/var/app -ENV BUNDLE_VERSION=2.5.9 +ENV BUNDLE_VERSION=2.5.21 ENV BUNDLE_PATH=/usr/local/bundle/gems ENV TMP_PATH=/tmp/ ENV RAILS_LOG_TO_STDOUT=true diff --git a/config/environments/production.rb b/config/environments/production.rb index f541929a..d0d64c4d 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -1,4 +1,6 @@ -require "active_support/core_ext/integer/time" +# frozen_string_literal: true + +require 'active_support/core_ext/integer/time' Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb. @@ -27,7 +29,11 @@ Rails.application.configure do # config.assets.css_compressor = :sass # Do not fallback to assets pipeline if a precompiled asset is missed. - config.assets.compile = false + config.assets.compile = true + + config.assets.content_type = { + geojson: 'application/geo+json' + } # Enable serving of images, stylesheets, and JavaScripts from an asset server. # config.asset_host = "http://assets.example.com" @@ -49,20 +55,20 @@ Rails.application.configure do # config.assume_ssl = 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('APPLICATION_PROTOCOL', 'http').downcase == 'https' - # Log to STDOUT by default - config.logger = ActiveSupport::Logger.new(STDOUT) - .tap { |logger| logger.formatter = ::Logger::Formatter.new } - .then { |logger| ActiveSupport::TaggedLogging.new(logger) } + # Direct logs to STDOUT + config.logger = Logger.new($stdout) + config.lograge.enabled = true + config.lograge.formatter = Lograge::Formatters::Json.new # Prepend all log lines with the following tags. - config.log_tags = [ :request_id ] + config.log_tags = [:request_id] # Info include generic and useful information about system operation, but avoids logging too much # information to avoid inadvertent exposure of personally identifiable information (PII). If you # want to log everything, set the level to "debug". - config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") + config.log_level = ENV.fetch('RAILS_LOG_LEVEL', 'info') # Use a different cache store in production. # config.cache_store = :mem_cache_store @@ -95,4 +101,8 @@ Rails.application.configure do # ] # Skip DNS rebinding protection for the default health check endpoint. # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } + config.hosts << ENV.fetch('APPLICATION_HOST', 'localhost') + + hosts = ENV.fetch('APPLICATION_HOSTS', 'localhost') + config.hosts.concat(hosts.split(',')) if hosts.present? end diff --git a/docker-compose.production.yml b/docker-compose.production.yml new file mode 100644 index 00000000..e6a39b90 --- /dev/null +++ b/docker-compose.production.yml @@ -0,0 +1,158 @@ +networks: + dawarich: +services: + dawarich_redis: + image: redis:7.0-alpine + container_name: dawarich_redis + command: redis-server + networks: + - dawarich + volumes: + - shared_data:/var/shared/redis + restart: always + healthcheck: + test: [ "CMD", "redis-cli", "--raw", "incr", "ping" ] + interval: 10s + retries: 5 + start_period: 30s + timeout: 10s + dawarich_db: + image: postgres:14.2-alpine + container_name: dawarich_db + volumes: + - db_data:/var/lib/postgresql/data + - shared_data:/var/shared + networks: + - dawarich + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + restart: always + healthcheck: + test: [ "CMD-SHELL", "pg_isready -U postgres -d dawarich_development" ] + interval: 10s + retries: 5 + start_period: 30s + timeout: 10s + dawarich_app: + image: freikin/dawarich:latest + container_name: dawarich_app + volumes: + - gem_cache:/usr/local/bundle/gems_app + - public:/var/app/public + - watched:/var/app/tmp/imports/watched + networks: + - dawarich + ports: + - 3000:3000 + # - 9394:9394 # Prometheus exporter, uncomment if needed + stdin_open: true + tty: true + entrypoint: dev-entrypoint.sh + command: ['bin/dev'] + restart: on-failure + environment: + RAILS_ENV: production + REDIS_URL: redis://dawarich_redis:6379/0 + DATABASE_HOST: dawarich_db + DATABASE_USERNAME: postgres + DATABASE_PASSWORD: password + DATABASE_NAME: dawarich_development + MIN_MINUTES_SPENT_IN_CITY: 60 + APPLICATION_HOST: localhost + APPLICATION_HOSTS: localhost + TIME_ZONE: Europe/London + APPLICATION_PROTOCOL: http + DISTANCE_UNIT: km + PHOTON_API_HOST: photon.komoot.io + PHOTON_API_USE_HTTPS: true + PROMETHEUS_EXPORTER_ENABLED: false + PROMETHEUS_EXPORTER_HOST: 0.0.0.0 + PROMETHEUS_EXPORTER_PORT: 9394 + logging: + driver: "json-file" + options: + max-size: "100m" + max-file: "5" + healthcheck: + test: [ "CMD-SHELL", "wget -qO - http://127.0.0.1:3000/api/v1/health | grep -q '\"status\"\\s*:\\s*\"ok\"'" ] + interval: 10s + retries: 30 + start_period: 30s + timeout: 10s + depends_on: + dawarich_db: + condition: service_healthy + restart: true + dawarich_redis: + condition: service_healthy + restart: true + deploy: + resources: + limits: + cpus: '0.50' # Limit CPU usage to 50% of one core + memory: '2G' # Limit memory usage to 2GB + dawarich_sidekiq: + image: freikin/dawarich:latest + container_name: dawarich_sidekiq + volumes: + - gem_cache:/usr/local/bundle/gems_sidekiq + - public:/var/app/public + - watched:/var/app/tmp/imports/watched + networks: + - dawarich + stdin_open: true + tty: true + entrypoint: dev-entrypoint.sh + command: ['sidekiq'] + restart: on-failure + environment: + RAILS_ENV: production + REDIS_URL: redis://dawarich_redis:6379/0 + DATABASE_HOST: dawarich_db + DATABASE_USERNAME: postgres + DATABASE_PASSWORD: password + DATABASE_NAME: dawarich_development + APPLICATION_HOST: localhost + APPLICATION_HOSTS: localhost + BACKGROUND_PROCESSING_CONCURRENCY: 10 + APPLICATION_PROTOCOL: http + DISTANCE_UNIT: km + PHOTON_API_HOST: photon.komoot.io + PHOTON_API_USE_HTTPS: true + PROMETHEUS_EXPORTER_ENABLED: false + PROMETHEUS_EXPORTER_HOST: dawarich_app + PROMETHEUS_EXPORTER_PORT: 9394 + logging: + driver: "json-file" + options: + max-size: "100m" + max-file: "5" + healthcheck: + test: [ "CMD-SHELL", "bundle exec sidekiqmon processes | grep $${HOSTNAME}" ] + interval: 10s + retries: 30 + start_period: 30s + timeout: 10s + depends_on: + dawarich_db: + condition: service_healthy + restart: true + dawarich_redis: + condition: service_healthy + restart: true + dawarich_app: + condition: service_healthy + restart: true + deploy: + resources: + limits: + cpus: '0.50' # Limit CPU usage to 50% of one core + memory: '2G' # Limit memory usage to 2GB + +volumes: + db_data: + gem_cache: + shared_data: + public: + watched: diff --git a/prod-docker-entrypoint.sh b/prod-docker-entrypoint.sh new file mode 100644 index 00000000..52bac19a --- /dev/null +++ b/prod-docker-entrypoint.sh @@ -0,0 +1,51 @@ +#!/bin/sh + +unset BUNDLE_PATH +unset BUNDLE_BIN + +set -e + +echo "Environment: $RAILS_ENV" + +# set env var defaults +DATABASE_HOST=${DATABASE_HOST:-"dawarich_db"} +DATABASE_PORT=${DATABASE_PORT:-5432} +DATABASE_USERNAME=${DATABASE_USERNAME:-"postgres"} +DATABASE_PASSWORD=${DATABASE_PASSWORD:-"password"} +DATABASE_NAME=${DATABASE_NAME:-"dawarich_production"} + +# Remove pre-existing puma/passenger server.pid +rm -f $APP_PATH/tmp/pids/server.pid + +# Wait for the database to be ready +until nc -zv $DATABASE_HOST ${DATABASE_PORT:-5432}; do + echo "Waiting for PostgreSQL to be ready..." + sleep 1 +done + +# Install gems +gem update --system 3.5.23 +gem install bundler --version '2.5.21' + +# Create the database +if [ "$(psql "postgres://$DATABASE_USERNAME:$DATABASE_PASSWORD@$DATABASE_HOST:$DATABASE_PORT" -XtAc "SELECT 1 FROM pg_database WHERE datname='$DATABASE_NAME'")" = '1' ]; then + echo "Database $DATABASE_NAME already exists, skipping creation..." +else + echo "Creating database $DATABASE_NAME..." + bundle exec rails db:create +fi + +# Run database migrations +echo "PostgreSQL is ready. Running database migrations..." +bundle exec rails db:prepare + +# Run data migrations +echo "Running DATA migrations..." +bundle exec rake data:migrate + +# Run seeds +echo "Running seeds..." +bundle exec rake db:seed + +# run passed commands +bundle exec ${@}