Commit bf140a70 by Ivan

feat: update dockerfile

parent dec5337b
......@@ -13,6 +13,7 @@
# Ignore all default key files.
/config/master.key
/config/credentials/*.key
/registry_password.key
# Ignore all logfiles and tempfiles.
/log/*
......
......@@ -32,6 +32,7 @@
# Ignore master key for decrypting credentials and more.
/config/master.key
/registry_password.key
# Vite Ruby
/public/vite*
......
......@@ -10,8 +10,9 @@
# Use a GITHUB_TOKEN if private repositories are needed for the image
# GITHUB_TOKEN=$(gh config get -h github.com oauth_token)
# Grab the registry password from ENV
KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD
# Set the Docker registry password directly
# Replace 'your-docker-password' with your actual Docker Hub password
KAMAL_REGISTRY_PASSWORD=$(cat registry_password.key)
# Improve security by using a password manager. Never check config/master.key into git!
RAILS_MASTER_KEY=$(cat config/master.key)
# syntax=docker/dockerfile:1
# check=error=true
# This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand:
# docker build -t img_manager .
# docker run -d -p 80:80 -e RAILS_MASTER_KEY=<value from config/master.key> --name img_manager img_manager
# For a containerized dev environment, see Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html
# This Dockerfile is designed for Git-based deployment with Kamal
# It pulls code from Git during deployment and uses external volumes for code, build artifacts, and data
# Make sure RUBY_VERSION matches the Ruby version in .ruby-version
ARG RUBY_VERSION=3.2.2
FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base
FROM docker.io/library/ruby:$RUBY_VERSION-slim
# Rails app lives here
WORKDIR /rails
# Install base packages
# Install base packages including Git for code pulling
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y curl libjemalloc2 libvips sqlite3 && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
apt-get install --no-install-recommends -y \
curl \
libjemalloc2 \
libvips \
sqlite3 \
nodejs \
npm \
netcat-openbsd \
git \
build-essential \
pkg-config \
&& rm -rf /var/lib/apt/lists /var/cache/apt/archives
# Set production environment
ENV RAILS_ENV="production" \
......@@ -25,43 +32,35 @@ ENV RAILS_ENV="production" \
BUNDLE_PATH="/usr/local/bundle" \
BUNDLE_WITHOUT="development"
# Throw-away build stage to reduce size of final image
FROM base AS build
# Install packages needed to build gems
RUN apt-get update -qq && \
apt-get install --no-install-recommends -y build-essential git pkg-config && \
rm -rf /var/lib/apt/lists /var/cache/apt/archives
# Install application 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/
# Final stage for app image
FROM base
# Copy built artifacts: gems, application
COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
COPY --from=build /rails /rails
# Create directories for mounted volumes
RUN mkdir -p \
/rails/code \
/rails/storage \
/rails/public/uploads \
/rails/public/assets \
/rails/public/vite \
/rails/node_modules \
/rails/tmp \
/rails/log \
/rails/tmp/pids \
/rails/tmp/cache \
/rails/tmp/sockets
# Declare volumes for persistent storage
VOLUME ["/rails/code", "/rails/storage", "/rails/public/uploads", "/rails/public/vite", "/rails/node_modules", "/rails/log", "/rails/tmp"]
# Copy entrypoint script
COPY bin/docker-entrypoint /rails/bin/
RUN chmod +x /rails/bin/docker-entrypoint
# Run and own only the runtime files as a non-root user for security
RUN groupadd --system --gid 1000 rails && \
useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \
chown -R rails:rails db log storage tmp
chown -R rails:rails /rails
USER 1000:1000
# Entrypoint prepares the database.
# Entrypoint pulls code, installs dependencies, and prepares the application
ENTRYPOINT ["/rails/bin/docker-entrypoint"]
# Start server via Thruster by default, this can be overwritten at runtime
......
......@@ -35,7 +35,7 @@ module Authentication
def request_authentication
session[:return_to_after_authenticating] = request.url
redirect_to new_sessions_path
redirect_to new_session_path
end
def after_authentication_url
......
......@@ -10,7 +10,7 @@ class PasswordsController < ApplicationController
PasswordsMailer.reset(user).deliver_later
end
redirect_to new_sessions_path, notice: "Password reset instructions sent (if user with that email address exists)."
redirect_to new_session_path, notice: "Password reset instructions sent (if user with that email address exists)."
end
def edit
......@@ -18,7 +18,7 @@ class PasswordsController < ApplicationController
def update
if @user.update(params.permit(:password, :password_confirmation))
redirect_to new_sessions_path, notice: "Password has been reset."
redirect_to new_session_path, notice: "Password has been reset."
else
redirect_to edit_password_path(params[:token]), alert: "Passwords did not match."
end
......
#!/bin/bash -e
echo "Starting deployment process..."
# Enable jemalloc for reduced memory usage and latency.
# Enable jemalloc for reduced memory usage and latency
if [ -z "${LD_PRELOAD+x}" ]; then
LD_PRELOAD=$(find /usr/lib -name libjemalloc.so.2 -print -quit)
export LD_PRELOAD
fi
# If running the rails server then create or migrate existing database
# Ensure all required directories exist with proper permissions
echo "Setting up directories..."
mkdir -p /rails/code /rails/storage /rails/public/uploads /rails/public/assets /rails/public/vite /rails/node_modules /rails/log /rails/tmp/pids /rails/tmp/cache /rails/tmp/sockets
# Set very permissive permissions for all directories
chmod -R 777 /rails/storage /rails/public/uploads /rails/public/assets /rails/public/vite /rails/node_modules /rails/log /rails/tmp
chown -R rails:rails /rails/storage /rails/public/uploads /rails/public/assets /rails/public/vite /rails/node_modules /rails/log /rails/tmp
# Verify the storage directory exists and has proper permissions
echo "Verifying storage directory permissions:"
ls -la /rails | grep storage
# Test if storage directory is writable
echo "Testing storage directory write access..."
if touch /rails/storage/test_write_access; then
echo "✅ Storage directory is writable"
rm /rails/storage/test_write_access
else
echo "❌ ERROR: Cannot write to storage directory. Check volume mount and permissions."
# Continue anyway, but log the error
fi
# Wait for external volumes to be properly mounted
sleep 5
# Check if code directory is empty or if we need to clone/pull the repository
if [ -z "${GIT_REPOSITORY}" ]; then
echo "⚠️ GIT_REPOSITORY environment variable not set. Using mounted code directory."
else
echo "🔄 Setting up code from Git repository: ${GIT_REPOSITORY}"
# Check if we already have the repository
if [ -d "/rails/code/.git" ]; then
echo "Git repository exists, pulling latest changes..."
cd /rails/code
git fetch
# Check if we need to checkout a specific branch or tag
if [ -n "${GIT_BRANCH}" ]; then
echo "Checking out branch/tag: ${GIT_BRANCH}"
git checkout ${GIT_BRANCH}
git pull origin ${GIT_BRANCH}
else
echo "No branch specified, pulling latest from current branch"
git pull
fi
else
echo "Cloning repository..."
# Clone the repository
if [ -n "${GIT_BRANCH}" ]; then
git clone --branch ${GIT_BRANCH} ${GIT_REPOSITORY} /rails/code
else
git clone ${GIT_REPOSITORY} /rails/code
fi
fi
echo "✅ Code updated from Git repository"
fi
# Create symbolic links from code directory to Rails app directory
echo "Setting up symbolic links..."
cd /rails/code
# Link important directories and files to maintain Rails structure
for dir in app bin config db lib public vendor; do
if [ -d "/rails/code/$dir" ]; then
ln -sfn /rails/code/$dir /rails/$dir
fi
done
# Link important files
for file in Gemfile Gemfile.lock Rakefile config.ru; do
if [ -f "/rails/code/$file" ]; then
ln -sf /rails/code/$file /rails/$file
fi
done
# Link node_modules if it exists in the mounted volume
if [ -d "/rails/node_modules" ]; then
ln -sfn /rails/node_modules /rails/code/node_modules
fi
# If running the rails server then install dependencies and prepare the database
if [ "${@: -2:1}" == "./bin/rails" ] && [ "${@: -1:1}" == "server" ]; then
./bin/rails db:prepare
cd /rails
# Install Ruby dependencies if needed
echo "Installing Ruby dependencies..."
if [ ! -d "${BUNDLE_PATH}/ruby" ] || [ "$FORCE_BUNDLE_INSTALL" = "true" ]; then
bundle install
echo "✅ Ruby dependencies installed"
else
echo "✅ Ruby dependencies already installed"
fi
# Install Node.js dependencies if needed
echo "Installing Node.js dependencies..."
if [ ! -d "/rails/node_modules/node_modules" ] || [ "$FORCE_NPM_INSTALL" = "true" ]; then
npm install
echo "✅ Node.js dependencies installed"
else
echo "✅ Node.js dependencies already installed"
fi
# Prepare database files with proper permissions
echo "Ensuring database files exist with proper permissions..."
# Create database directory with proper permissions
mkdir -p /rails/storage
chmod -R 777 /rails/storage
chown -R rails:rails /rails/storage
echo "Creating database files if they don't exist..."
# Create database files if they don't exist
for db_file in production.sqlite3 production_cache.sqlite3 production_queue.sqlite3 production_cable.sqlite3; do
if [ ! -f "/rails/storage/$db_file" ]; then
echo "Creating /rails/storage/$db_file"
touch "/rails/storage/$db_file"
chmod 666 "/rails/storage/$db_file"
chown rails:rails "/rails/storage/$db_file"
else
echo "Database file /rails/storage/$db_file already exists"
# Make sure existing file has correct permissions
chmod 666 "/rails/storage/$db_file"
chown rails:rails "/rails/storage/$db_file"
fi
done
# Double-check permissions and ownership
echo "Database directory permissions:"
ls -la /rails/storage
# Test if we can write to the database file
echo "Testing database file write access..."
if sqlite3 /rails/storage/production.sqlite3 "PRAGMA user_version;" > /dev/null 2>&1; then
echo "✅ Database file is writable"
else
echo "❌ ERROR: Cannot write to database file. Check volume mount and permissions."
# Try to fix permissions again with more aggressive approach
echo "Attempting more aggressive permission fix..."
chown -R rails:rails /rails/storage
chmod -R 777 /rails/storage
chmod 666 /rails/storage/*.sqlite3
# Try to create an empty database structure
echo "Attempting to initialize empty database..."
sqlite3 /rails/storage/production.sqlite3 "CREATE TABLE IF NOT EXISTS schema_migrations (version varchar(255) NOT NULL); CREATE UNIQUE INDEX unique_schema_migrations ON schema_migrations (version);" || true
fi
# Prepare the database with retry logic
echo "Preparing database..."
# First try to manually create schema_migrations table to avoid common issues
echo "Attempting to create schema_migrations table manually..."
for db_file in /rails/storage/production.sqlite3 /rails/storage/production_cache.sqlite3 /rails/storage/production_queue.sqlite3 /rails/storage/production_cable.sqlite3; do
echo "Initializing $db_file..."
sqlite3 "$db_file" "CREATE TABLE IF NOT EXISTS schema_migrations (version varchar(255) NOT NULL); CREATE UNIQUE INDEX IF NOT EXISTS unique_schema_migrations ON schema_migrations (version);" || true
done
# Create empty schema.rb file to help Rails recognize the database state
mkdir -p /rails/db
if [ ! -f "/rails/db/schema.rb" ]; then
echo "Creating empty schema.rb file..."
cat > /rails/db/schema.rb << 'EOL'
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# This file is the source Rails uses to define your schema when running `bin/rails
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.1].define(version: 0) do
# These are extensions that must be enabled in order to support this database
end
EOL
fi
# Check if we should skip database preparation
if [ "${SKIP_DB_PREPARATION}" = "true" ]; then
echo "⚠️ SKIP_DB_PREPARATION is set to true, skipping database preparation"
echo "✅ Using existing database files without migrations"
SUCCESS=true
else
# Now try to run db:prepare with multiple attempts
for i in {1..3}; do
echo "Database preparation attempt $i..."
# Print current environment for debugging
echo "Current environment:"
echo "- Working directory: $(pwd)"
echo "- User: $(whoami)"
echo "- Storage directory contents:"
ls -la /rails/storage
echo "- Database config:"
cat /rails/config/database.yml | grep -A 10 production
# Try different database commands with increasing aggressiveness
if [ $i -eq 1 ]; then
echo "Trying db:prepare..."
RAILS_ENV=production ./bin/rails db:prepare --trace && SUCCESS=true && break
elif [ $i -eq 2 ]; then
echo "Trying db:migrate:status..."
RAILS_ENV=production ./bin/rails db:migrate:status --trace || true
echo "Trying db:schema:load..."
RAILS_ENV=production ./bin/rails db:schema:load --trace && SUCCESS=true && break
else
echo "Trying db:setup..."
RAILS_ENV=production ./bin/rails db:setup --trace && SUCCESS=true && break
fi
echo "⚠️ Database preparation attempt $i failed, retrying..."
# Try to fix permissions again
echo "Fixing permissions again..."
chmod -R 777 /rails/storage
chmod 666 /rails/storage/*.sqlite3
chown -R rails:rails /rails/storage
sleep 5
done
fi
if [ "$SUCCESS" = "true" ]; then
echo "✅ Database prepared successfully"
else
echo "⚠️ WARNING: Database preparation failed after 3 attempts, but continuing startup"
echo "The application may not function correctly until database issues are resolved"
fi
# Build Vite assets if needed
if [ ! -d "/rails/public/vite" ] || [ -z "$(ls -A /rails/public/vite)" ] || [ "$FORCE_VITE_BUILD" = "true" ]; then
echo "Building Vite assets..."
bundle exec vite build
echo "✅ Vite assets built"
else
echo "✅ Vite assets already exist"
fi
# Create health check file
touch /rails/tmp/healthy
echo "✅ Application ready to start"
fi
echo "🚀 Starting application..."
exec "${@}"
#!/bin/bash
# This script sets up the required directories and permissions on the server
# Run this script on the server before deploying
echo "Setting up directories and permissions for img-manager..."
# Create all required directories
mkdir -p /root/img_manager/code
mkdir -p /root/img_manager/storage
mkdir -p /root/img_manager/uploads
mkdir -p /root/img_manager/public/vite
mkdir -p /root/img_manager/node_modules
mkdir -p /root/img_manager/logs
mkdir -p /root/img_manager/tmp
# Set very permissive permissions
chmod -R 777 /root/img_manager
# Create empty database files with proper permissions
touch /root/img_manager/storage/production.sqlite3
touch /root/img_manager/storage/production_cache.sqlite3
touch /root/img_manager/storage/production_queue.sqlite3
touch /root/img_manager/storage/production_cable.sqlite3
chmod 666 /root/img_manager/storage/*.sqlite3
# Initialize the database files with schema_migrations tables
echo "Initializing database files with schema_migrations tables..."
for db_file in /root/img_manager/storage/production.sqlite3 /root/img_manager/storage/production_cache.sqlite3 /root/img_manager/storage/production_queue.sqlite3 /root/img_manager/storage/production_cable.sqlite3; do
sqlite3 "$db_file" "CREATE TABLE IF NOT EXISTS schema_migrations (version varchar(255) NOT NULL); CREATE UNIQUE INDEX IF NOT EXISTS unique_schema_migrations ON schema_migrations (version);" || echo "Failed to initialize $db_file"
done
echo "Checking SELinux status..."
if command -v getenforce &> /dev/null; then
selinux_status=$(getenforce)
echo "SELinux status: $selinux_status"
if [ "$selinux_status" == "Enforcing" ]; then
echo "SELinux is enforcing. Setting proper contexts..."
# Set SELinux context for the directories
if command -v chcon &> /dev/null; then
chcon -Rt svirt_sandbox_file_t /root/img_manager
else
echo "chcon command not found. Unable to set SELinux context."
fi
fi
else
echo "getenforce command not found. Unable to check SELinux status."
fi
echo "Directory permissions:"
ls -la /root/img_manager
echo "Storage directory permissions:"
ls -la /root/img_manager/storage
echo "Setup complete!"
......@@ -21,21 +21,21 @@ test:
database: storage/test.sqlite3
# Store production database in the storage/ directory, which by default
# is mounted as a persistent Docker volume in config/deploy.yml.
# Store production database in the /rails/storage/ directory with absolute paths
# This ensures the database files are found regardless of current directory
production:
primary:
<<: *default
database: storage/production.sqlite3
database: /rails/storage/production.sqlite3
cache:
<<: *default
database: storage/production_cache.sqlite3
database: /rails/storage/production_cache.sqlite3
migrations_paths: db/cache_migrate
queue:
<<: *default
database: storage/production_queue.sqlite3
database: /rails/storage/production_queue.sqlite3
migrations_paths: db/queue_migrate
cable:
<<: *default
database: storage/production_cable.sqlite3
database: /rails/storage/production_cable.sqlite3
migrations_paths: db/cable_migrate
......@@ -2,12 +2,12 @@
service: img_manager
# Name of the container image.
image: your-user/img_manager
image: mumumumushu/img_manager
# Deploy to these servers.
servers:
web:
- 192.168.0.1
- 45.78.59.154
# job:
# hosts:
# - 192.168.0.1
......@@ -18,14 +18,16 @@ servers:
#
# Note: If using Cloudflare, set encryption mode in SSL/TLS setting to "Full" to enable CF-to-app encryption.
proxy:
ssl: true
host: app.example.com
# ssl: true
host: 45.78.59.154
# If you have a domain name, use it instead of the IP address
# host: your-domain.com
# Credentials for your image host.
registry:
# Specify the registry server, if you're not using Docker Hub
# server: registry.digitalocean.com / ghcr.io / ...
username: your-user
username: mumumumushu
# Always use an access token rather than real password when possible.
password:
......@@ -35,9 +37,26 @@ registry:
env:
secret:
- RAILS_MASTER_KEY
# Add Git credentials if needed for private repositories
# - GIT_CREDENTIALS
clear:
# Git repository configuration
GIT_REPOSITORY: http://git.tallty.com/mumumumushu/img-manager.git
GIT_BRANCH: main
# Force rebuild flags (set to true when you want to force rebuild)
FORCE_BUNDLE_INSTALL: true
FORCE_NPM_INSTALL: true
FORCE_VITE_BUILD: true
# Database configuration
RAILS_ENV: production
DATABASE_URL: sqlite3:///rails/storage/production.sqlite3
# Disable database migrations during startup to avoid SQLite issues
SKIP_DB_PREPARATION: true
# Run the Solid Queue Supervisor inside the web server's Puma process to do jobs.
# When you start using multiple servers, you should split out job processing to a dedicated machine.
SOLID_QUEUE_IN_PUMA: true
# Set number of processes dedicated to Solid Queue (default: 1)
......@@ -47,11 +66,10 @@ env:
# WEB_CONCURRENCY: 2
# Match this to any external database server to configure Active Record correctly
# Use img_manager-db for a db accessory server on same machine via local kamal docker network.
# DB_HOST: 192.168.0.2
# Log everything from Rails
# RAILS_LOG_LEVEL: debug
RAILS_LOG_LEVEL: debug
# Aliases are triggered with "bin/kamal <alias>". You can overwrite arguments on invocation:
# "bin/kamal logs -r job" will tail logs from the first server in the job section.
......@@ -60,18 +78,47 @@ aliases:
shell: app exec --interactive --reuse "bash"
logs: app logs -f
dbc: app exec --interactive --reuse "bin/rails dbconsole"
assets: app exec --interactive --reuse "bundle exec vite build"
migrate: app exec --interactive --reuse "bin/rails db:migrate"
# Use a persistent storage volume for sqlite database files and local Active Storage files.
# Recommended to change this to a mounted volume path that is backed up off server.
# Use persistent storage volumes for external storage of code, files, and database
# These volumes will be mounted to the container and persist between deployments
volumes:
- "img_manager_storage:/rails/storage"
# Git code repository storage
- "/root/img_manager/code:/rails/code"
# Database storage with simpler configuration (no Z flag to avoid SELinux issues)
- "/root/img_manager/storage:/rails/storage"
# Uploaded files storage
- "/root/img_manager/uploads:/rails/public/uploads"
# Vite assets storage
- "/root/img_manager/public/vite:/rails/public/vite"
# Node modules storage
- "/root/img_manager/node_modules:/rails/node_modules"
# Logs storage
- "/root/img_manager/logs:/rails/log"
# Tmp directory for pids and other temporary files
- "/root/img_manager/tmp:/rails/tmp"
# Bridge fingerprinted assets, like JS and CSS, between versions to avoid
# hitting 404 on in-flight requests. Combines all files from new and old
# version inside the asset_path.
asset_path: /rails/public/assets
# Disable asset bridging for now to fix deployment issues
asset_path: false
# Deployment configuration for better reliability
# Increase the time to wait for a container to become ready
# deploy_timeout: 180
# # Increase the time to wait for a container to drain
# drain_timeout: 60
# # Increase the time to wait for a container to boot after it is running
# readiness_delay: 30
# Note: Run the setup script manually before deployment:
# scp ./bin/setup_volumes.sh root@45.78.59.154:/root/setup_volumes.sh
# ssh root@45.78.59.154 'chmod +x /root/setup_volumes.sh && /root/setup_volumes.sh'
# Configure the image builder.
builder:
......
dckr_pat_3-xxxx
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment