diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..41a8c0e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,31 @@ +# Git +.git + +# Build artifacts +app/_build +app/deps +blogex/_build +blogex/deps + +# Dev/test only +app/test +blogex/test +app/.formatter.exs +blogex/.formatter.exs + +# IDE +.devcontainer +.claude + +# Documentation +*.md +!app/README.md + +# Misc +app/tmp +app/cover +app/doc +blogex/doc + +# Dokku setup (may contain secrets) +dokku-setup.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b842451 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Dokku setup (may contain secrets) +dokku-setup.sh diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e0f7035 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,86 @@ +# Dockerfile for Dokku deployment +# Multi-stage build for Phoenix/Elixir app with monorepo layout + +ARG ELIXIR_VERSION=1.18.3 +ARG OTP_VERSION=27.2.4 +ARG DEBIAN_VERSION=bookworm-20260316-slim + +ARG BUILDER_IMAGE="docker.io/hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}" +ARG RUNNER_IMAGE="docker.io/debian:${DEBIAN_VERSION}" + +# ============================================================================= +# Build stage +# ============================================================================= +FROM ${BUILDER_IMAGE} AS builder + +RUN apt-get update -y && apt-get install -y build-essential git \ + && apt-get clean && rm -f /var/lib/apt/lists/*_* + +WORKDIR /build + +# Install hex + rebar +RUN mix local.hex --force && \ + mix local.rebar --force + +ENV MIX_ENV="prod" + +# Copy blogex dependency first (changes less often) +COPY blogex /build/blogex + +# Copy app dependency files first for better layer caching +COPY app/mix.exs app/mix.lock /build/app/ +WORKDIR /build/app + +RUN mix deps.get --only $MIX_ENV +RUN mkdir config + +# Copy compile-time config files +COPY app/config/config.exs app/config/${MIX_ENV}.exs config/ +RUN mix deps.compile + +# Copy application source and compile +COPY app/priv priv +COPY app/assets assets +COPY app/lib lib +COPY app/rel rel +COPY app/config/runtime.exs config/ + +RUN mix compile + +# Build assets after compile (phoenix-colocated hooks need compiled app) +RUN mix assets.deploy + +# Build the release +RUN mix release + +# ============================================================================= +# Runtime stage +# ============================================================================= +FROM ${RUNNER_IMAGE} + +RUN apt-get update -y && \ + apt-get install -y libstdc++6 openssl libncurses5 locales ca-certificates \ + && apt-get clean && rm -f /var/lib/apt/lists/*_* + +# Set the locale +RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen +ENV LANG en_US.UTF-8 +ENV LANGUAGE en_US:en +ENV LC_ALL en_US.UTF-8 + +WORKDIR /app + +RUN chown nobody /app +ENV MIX_ENV="prod" + +# Copy the release from the build stage +COPY --from=builder --chown=nobody:root /build/app/_build/${MIX_ENV}/rel/firehose ./ + +USER nobody + +# Dokku uses the EXPOSE port for routing +EXPOSE 4000 + +ENV PHX_SERVER=true + +CMD ["/app/bin/server"] diff --git a/app.json b/app.json new file mode 100644 index 0000000..45344ec --- /dev/null +++ b/app.json @@ -0,0 +1,8 @@ +{ + "name": "firehose", + "scripts": { + "dokku": { + "postdeploy": "/app/bin/migrate" + } + } +} diff --git a/app/lib/firehose/release.ex b/app/lib/firehose/release.ex new file mode 100644 index 0000000..a9d3e6a --- /dev/null +++ b/app/lib/firehose/release.ex @@ -0,0 +1,31 @@ +defmodule Firehose.Release do + @moduledoc """ + Tasks for production releases (e.g., database migrations). + + Usage from Dokku: + dokku run APP_NAME /app/bin/migrate + """ + + @app :firehose + + 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.load(@app) + end +end diff --git a/app/rel/overlays/bin/migrate b/app/rel/overlays/bin/migrate new file mode 100755 index 0000000..c046585 --- /dev/null +++ b/app/rel/overlays/bin/migrate @@ -0,0 +1,6 @@ +#!/bin/sh +set -eu + +cd -P -- "$(dirname -- "$0")"/.. + +exec ./bin/firehose eval Firehose.Release.migrate diff --git a/app/rel/overlays/bin/server b/app/rel/overlays/bin/server new file mode 100755 index 0000000..f412278 --- /dev/null +++ b/app/rel/overlays/bin/server @@ -0,0 +1,6 @@ +#!/bin/sh +set -eu + +cd -P -- "$(dirname -- "$0")"/.. + +PHX_SERVER=true exec ./bin/firehose start diff --git a/dokku-setup.sh.sample b/dokku-setup.sh.sample new file mode 100644 index 0000000..87d85b8 --- /dev/null +++ b/dokku-setup.sh.sample @@ -0,0 +1,49 @@ +#!/bin/bash +# dokku-setup.sh.sample - Set up Dokku app for firehose +# +# USAGE: +# 1. Copy to dokku-setup.sh: cp dokku-setup.sh.sample dokku-setup.sh +# 2. Fill in any empty strings below +# 3. Run on Dokku server: ./dokku-setup.sh +# +# Do NOT commit dokku-setup.sh (contains secrets) + +set -e + +APP="firehose" # <-- change to your desired Dokku app name / hostname +PHX_HOST="$APP" # <-- change to your full domain, e.g., firehose.example.com + +# Auto-generate secrets +SECRET_KEY_BASE=$(openssl rand -base64 64 | tr -d '\n') + +echo "==> Creating Dokku app: $APP" +dokku apps:create "$APP" || echo "App may already exist" + +echo "==> Creating PostgreSQL database" +dokku postgres:create "${APP}-db" || echo "Database may already exist" +dokku postgres:link "${APP}-db" "$APP" || echo "Database may already be linked" + +echo "==> Setting environment variables" +dokku config:set --no-restart "$APP" \ + SECRET_KEY_BASE="$SECRET_KEY_BASE" \ + PHX_HOST="$PHX_HOST" \ + PORT="4000" + +# Optional: set custom domain (uncomment and edit) +# dokku domains:set "$APP" "$PHX_HOST" + +# Optional: set up SSL with Let's Encrypt (uncomment) +# dokku letsencrypt:enable "$APP" + +echo "" +echo "==> Setup complete!" +echo "" +echo "Next steps:" +echo " 1. From your local machine, add the git remote:" +echo " git remote add dokku dokku@YOUR_SERVER:$APP" +echo "" +echo " 2. Push to deploy:" +echo " git push dokku main" +echo "" +echo " 3. Run migrations (first deploy):" +echo " dokku run $APP /app/bin/migrate"