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
23 changes: 23 additions & 0 deletions .env.docker.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Copy to .env, fill the secrets, then: docker compose up --build
# Generate each secret with: openssl rand -base64 32

NEXT_PUBLIC_APP_NAME=Align

# ── Auth (REQUIRED — generate your own, do not ship these) ──────────────
AUTH_SECRET=replace-with-openssl-rand-base64-32
BETTER_AUTH_SECRET=replace-with-openssl-rand-base64-32
TOTP_ENC_KEY=replace-with-openssl-rand-base64-32
BETTER_AUTH_URL=http://localhost:3000
NEXTAUTH_URL=http://localhost:3000

# ── First-run seed ──────────────────────────────────────────────────────
# SEED_ON_START=true creates default categories and one admin user below.
SEED_ON_START=true
USER1_EMAIL=admin@example.com
USER1_PASSWORD=changeme123
USER1_NAME=Admin

# ── Postgres (defaults are fine for local) ──────────────────────────────
POSTGRES_USER=align
POSTGRES_PASSWORD=align
POSTGRES_DB=align_dev
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,19 @@ Longer version in [apps/web/README.md](apps/web/README.md). Deploying to Vercel

Want to call it something other than "Align"? Set `NEXT_PUBLIC_APP_NAME` and it changes everywhere in the UI.

### Or with Docker

Brings up Postgres and the web app together — no local Node or Postgres needed:

```bash
cp .env.docker.example .env # then set the three secrets it lists
docker compose up --build # http://localhost:3000
```

The database starts first, migrations run automatically, and (with
`SEED_ON_START=true`) a first admin user is created from the `USER1_*` values in
your `.env`. Postgres is exposed on `localhost:5434` if you want to poke at it.

## Running the mobile app

```bash
Expand Down
12 changes: 12 additions & 0 deletions apps/web/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
node_modules
.next
.git
.vercel
coverage
.env
.env.*
!.env.example
Dockerfile
.dockerignore
npm-debug.log*
*.log
28 changes: 28 additions & 0 deletions apps/web/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# syntax=docker/dockerfile:1
# Self-contained image for the Align web app (Next.js 16 + Prisma + Postgres).
# Installs deps, generates the Prisma client and builds; at container start it
# applies migrations (and optionally seeds) then boots the production server.

FROM node:20-alpine AS build
RUN corepack enable
WORKDIR /app

# Install dependencies first against the committed lockfile for better caching.
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile

# Build. A placeholder DATABASE_URL keeps `prisma generate` / `next build`
# happy — no database is contacted at build time; the real URL is injected at
# runtime by docker-compose.
COPY . .
ENV DATABASE_URL="postgresql://placeholder:placeholder@localhost:5432/placeholder"
ENV NEXT_TELEMETRY_DISABLED=1
RUN pnpm prisma generate && pnpm build

ENV NODE_ENV=production
ENV PORT=3000
EXPOSE 3000

RUN chmod +x docker-entrypoint.sh
ENTRYPOINT ["./docker-entrypoint.sh"]
CMD ["pnpm", "start"]
24 changes: 24 additions & 0 deletions apps/web/docker-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/sh
set -e

# Apply migrations, retrying while Postgres finishes coming up.
echo "▶ Applying database migrations..."
n=0
until pnpm prisma migrate deploy; do
n=$((n + 1))
if [ "$n" -ge 10 ]; then
echo "✖ migrations failed after $n attempts"
exit 1
fi
echo " database not ready — retry $n/10 in 3s..."
sleep 3
done

# Optional first-run seed (categories + an admin user from USER1_* env vars).
if [ "${SEED_ON_START:-false}" = "true" ]; then
echo "▶ Seeding database..."
pnpm exec tsx prisma/seed.ts || echo "⚠ seed failed or already applied — continuing"
fi

echo "▶ Starting Align on :${PORT:-3000}"
exec "$@"
58 changes: 58 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Run the full Align web app locally (contributors / self-hosting):
#
# cp .env.docker.example .env
# # edit .env — set AUTH_SECRET / BETTER_AUTH_SECRET / TOTP_ENC_KEY
# docker compose up --build
#
# App: http://localhost:3000
# Postgres: localhost:5434 (user/pass/db: align / align / align_dev)
#
# The db service alone (without web) also backs local `next dev` — the app's
# prisma.ts uses the plain pg driver for any non-Neon host, so this Postgres
# works directly.

services:
db:
image: postgres:17
container_name: align-db
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER:-align}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-align}
POSTGRES_DB: ${POSTGRES_DB:-align_dev}
ports:
- "5434:5432"
volumes:
- align-db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-align} -d ${POSTGRES_DB:-align_dev}"]
interval: 5s
timeout: 5s
retries: 10

web:
build:
context: ./apps/web
container_name: align-web
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
DATABASE_URL: postgresql://${POSTGRES_USER:-align}:${POSTGRES_PASSWORD:-align}@db:5432/${POSTGRES_DB:-align_dev}
NEXT_PUBLIC_APP_NAME: ${NEXT_PUBLIC_APP_NAME:-Align}
AUTH_SECRET: ${AUTH_SECRET:?set AUTH_SECRET in .env (openssl rand -base64 32)}
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET:?set BETTER_AUTH_SECRET in .env (openssl rand -base64 32)}
BETTER_AUTH_URL: ${BETTER_AUTH_URL:-http://localhost:3000}
NEXTAUTH_URL: ${NEXTAUTH_URL:-http://localhost:3000}
TOTP_ENC_KEY: ${TOTP_ENC_KEY:?set TOTP_ENC_KEY in .env (openssl rand -base64 32)}
# First-run seed: categories + an admin user from the USER1_* values.
SEED_ON_START: ${SEED_ON_START:-true}
USER1_EMAIL: ${USER1_EMAIL:-admin@example.com}
USER1_PASSWORD: ${USER1_PASSWORD:-changeme123}
USER1_NAME: ${USER1_NAME:-Admin}
ports:
- "3000:3000"

volumes:
align-db-data:
Loading