add Docker deployment with Alpine image, release config and health check
- Alpine multi-stage Dockerfile (131 MB image) - Release overlays (bin/server, bin/migrate), env.sh, Release module - Health check endpoint at GET /health - Fly.io config with SQLite volume mount - Fix hardcoded paths in optimizer.ex and variant_cache.ex to use Application.app_dir/2 (breaks in releases where Plug.Static serves from a different directory than CWD) - strip_beams: true in release config - Optimised .dockerignore and .gitignore for mockup variants Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -13,11 +13,10 @@ defmodule SimpleshopTheme.Images.Optimizer do
|
||||
# Only affects <5% of users (legacy browsers without AVIF/WebP support)
|
||||
@pregenerated_formats [:avif, :webp]
|
||||
@thumb_size 200
|
||||
@cache_dir "priv/static/image_cache"
|
||||
@max_stored_width 2000
|
||||
@storage_quality 90
|
||||
|
||||
def cache_dir, do: @cache_dir
|
||||
def cache_dir, do: Application.app_dir(:simpleshop_theme, "priv/static/image_cache")
|
||||
def all_widths, do: @all_widths
|
||||
|
||||
@doc """
|
||||
@@ -72,7 +71,7 @@ defmodule SimpleshopTheme.Images.Optimizer do
|
||||
{:ok, :svg_skipped}
|
||||
|
||||
%{data: data, source_width: width} = image ->
|
||||
File.mkdir_p!(@cache_dir)
|
||||
File.mkdir_p!(cache_dir())
|
||||
|
||||
with {:ok, vips_image} <- Image.from_binary(data) do
|
||||
widths = applicable_widths(width)
|
||||
@@ -93,7 +92,7 @@ defmodule SimpleshopTheme.Images.Optimizer do
|
||||
end
|
||||
|
||||
defp generate_thumbnail(image, id) do
|
||||
path = Path.join(@cache_dir, "#{id}-thumb.jpg")
|
||||
path = Path.join(cache_dir(), "#{id}-thumb.jpg")
|
||||
|
||||
return_if_exists(path, fn ->
|
||||
with {:ok, thumb} <- Image.thumbnail(image, @thumb_size),
|
||||
@@ -104,7 +103,7 @@ defmodule SimpleshopTheme.Images.Optimizer do
|
||||
end
|
||||
|
||||
defp generate_variant(image, id, width, format) do
|
||||
path = Path.join(@cache_dir, "#{id}-#{width}.#{format_ext(format)}")
|
||||
path = Path.join(cache_dir(), "#{id}-#{width}.#{format_ext(format)}")
|
||||
|
||||
return_if_exists(path, fn ->
|
||||
with {:ok, resized} <- Image.thumbnail(image, width),
|
||||
@@ -140,12 +139,12 @@ defmodule SimpleshopTheme.Images.Optimizer do
|
||||
"""
|
||||
def disk_variants_exist?(image_id, source_width) do
|
||||
widths = applicable_widths(source_width)
|
||||
thumb = File.exists?(Path.join(@cache_dir, "#{image_id}-thumb.jpg"))
|
||||
thumb = File.exists?(Path.join(cache_dir(), "#{image_id}-thumb.jpg"))
|
||||
|
||||
variants =
|
||||
Enum.all?(widths, fn w ->
|
||||
Enum.all?(@pregenerated_formats, fn fmt ->
|
||||
File.exists?(Path.join(@cache_dir, "#{image_id}-#{w}.#{format_ext(fmt)}"))
|
||||
File.exists?(Path.join(cache_dir(), "#{image_id}-#{w}.#{format_ext(fmt)}"))
|
||||
end)
|
||||
end)
|
||||
|
||||
@@ -159,12 +158,12 @@ defmodule SimpleshopTheme.Images.Optimizer do
|
||||
"""
|
||||
def generate_variant_on_demand(image_data, image_id, width, format)
|
||||
when is_binary(image_data) and format in [:avif, :webp, :jpg] do
|
||||
path = Path.join(@cache_dir, "#{image_id}-#{width}.#{format_ext(format)}")
|
||||
path = Path.join(cache_dir(), "#{image_id}-#{width}.#{format_ext(format)}")
|
||||
|
||||
if File.exists?(path) do
|
||||
{:ok, path}
|
||||
else
|
||||
File.mkdir_p!(@cache_dir)
|
||||
File.mkdir_p!(cache_dir())
|
||||
|
||||
with {:ok, vips_image} <- Image.from_binary(image_data),
|
||||
{:ok, resized} <- Image.thumbnail(vips_image, width),
|
||||
|
||||
@@ -19,7 +19,7 @@ defmodule SimpleshopTheme.Images.VariantCache do
|
||||
alias SimpleshopTheme.Sync.ImageDownloadWorker
|
||||
import Ecto.Query
|
||||
|
||||
@mockup_dir "priv/static/mockups"
|
||||
defp mockup_dir, do: Application.app_dir(:simpleshop_theme, "priv/static/mockups")
|
||||
|
||||
def start_link(opts) do
|
||||
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
|
||||
@@ -87,9 +87,9 @@ defmodule SimpleshopTheme.Images.VariantCache do
|
||||
end
|
||||
|
||||
defp ensure_mockup_variants do
|
||||
if File.dir?(@mockup_dir) do
|
||||
if File.dir?(mockup_dir()) do
|
||||
sources =
|
||||
Path.wildcard(Path.join(@mockup_dir, "*.webp"))
|
||||
Path.wildcard(Path.join(mockup_dir(), "*.webp"))
|
||||
|> Enum.reject(&is_variant?/1)
|
||||
|
||||
missing = Enum.reject(sources, &mockup_variants_exist?/1)
|
||||
|
||||
32
lib/simpleshop_theme/release.ex
Normal file
32
lib/simpleshop_theme/release.ex
Normal file
@@ -0,0 +1,32 @@
|
||||
defmodule SimpleshopTheme.Release do
|
||||
@moduledoc """
|
||||
Release tasks that can be run via `bin/migrate` or `bin/simpleshop_theme eval`.
|
||||
|
||||
Migrations run automatically on startup (see Application), so this is mainly
|
||||
useful as a standalone tool for debugging or manual recovery.
|
||||
"""
|
||||
|
||||
@app :simpleshop_theme
|
||||
|
||||
def migrate do
|
||||
load_app()
|
||||
|
||||
for repo <- repos() do
|
||||
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
|
||||
end
|
||||
end
|
||||
|
||||
def rollback(repo, version) do
|
||||
load_app()
|
||||
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))
|
||||
end
|
||||
|
||||
defp repos do
|
||||
Application.fetch_env!(@app, :ecto_repos)
|
||||
end
|
||||
|
||||
defp load_app do
|
||||
Application.ensure_all_started(:ssl)
|
||||
Application.load(@app)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
defmodule SimpleshopThemeWeb.HealthController do
|
||||
use SimpleshopThemeWeb, :controller
|
||||
|
||||
def show(conn, _params) do
|
||||
json(conn, %{status: "ok"})
|
||||
end
|
||||
end
|
||||
@@ -52,6 +52,13 @@ defmodule SimpleshopThemeWeb.Router do
|
||||
post "/checkout", CheckoutController, :create
|
||||
end
|
||||
|
||||
# Health check (no auth, no theme loading — for load balancers and uptime monitors)
|
||||
scope "/", SimpleshopThemeWeb do
|
||||
pipe_through [:api]
|
||||
|
||||
get "/health", HealthController, :show
|
||||
end
|
||||
|
||||
# Cart API (session persistence for LiveView)
|
||||
scope "/api", SimpleshopThemeWeb do
|
||||
pipe_through [:browser]
|
||||
|
||||
Reference in New Issue
Block a user