Skip to content

Commit

Permalink
Optimize docker image size (#91)
Browse files Browse the repository at this point in the history
* Move prisma to runtime dependencies

* Optimize Dockerfile and build script

* Fix: remove mention of generated next-env.d.ts in Dockerfile

* Add missing reset.d.ts file to Dockerfile

* Remove compression steps from Dockerfile and entrypoint script

* Add an env file with mocked env vars added for Docker production builds

* Use server actions to get runtime env vars

* Refactor types and names

* Rollback serverActions, use parsed Zod object for runtime env

* Reintroduce featureFlags object to avoid passing secret envs to the frontend

* Improve string to boolean coercion

Co-authored-by: Sebastien Castiel <sebastien@castiel.me>

* Run prettier autoformat

* Fix type issue, rename function to match behaviour better

---------

Co-authored-by: Lauri Vuorela <lauri.vuorela@gmail.com>
Co-authored-by: Sebastien Castiel <sebastien@castiel.me>
  • Loading branch information
3 people authored Feb 14, 2024
1 parent 50525ad commit 2af0660
Show file tree
Hide file tree
Showing 12 changed files with 143 additions and 74 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ yarn-error.log*
# local env files
.env*.local
*.env
!scripts/build.env

# vercel
.vercel
Expand Down
53 changes: 39 additions & 14 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,22 +1,47 @@
FROM node:21-slim as base
FROM node:21-alpine as base

EXPOSE 3000/tcp
WORKDIR /usr/app
COPY ./ ./
COPY ./package.json \
./package-lock.json \
./next.config.js \
./tsconfig.json \
./reset.d.ts \
./tailwind.config.js \
./postcss.config.js ./
COPY ./scripts ./scripts
COPY ./prisma ./prisma
COPY ./src ./src

RUN apt update && \
apt install openssl -y && \
apt clean && \
apt autoclean && \
apt autoremove && \
RUN apk add --no-cache openssl && \
npm ci --ignore-scripts && \
npm install -g prisma && \
prisma generate
npx prisma generate

# env vars needed for build not to fail
ARG POSTGRES_PRISMA_URL
ARG POSTGRES_URL_NON_POOLING
ENV NEXT_TELEMETRY_DISABLED=1

COPY scripts/build.env .env
RUN npm run build

ENTRYPOINT ["/usr/app/scripts/container-entrypoint.sh"]
RUN rm -r .next/cache

FROM node:21-alpine as runtime-deps

WORKDIR /usr/app
COPY --from=base /usr/app/package.json /usr/app/package-lock.json ./
COPY --from=base /usr/app/prisma ./prisma

RUN npm ci --omit=dev --omit=optional --ignore-scripts && \
npx prisma generate

FROM node:21-alpine as runner

EXPOSE 3000/tcp
WORKDIR /usr/app

COPY --from=base /usr/app/package.json /usr/app/package-lock.json ./
COPY --from=runtime-deps /usr/app/node_modules ./node_modules
COPY ./public ./public
COPY ./scripts ./scripts
COPY --from=base /usr/app/prisma ./prisma
COPY --from=base /usr/app/.next ./.next

ENTRYPOINT ["/bin/sh", "/usr/app/scripts/container-entrypoint.sh"]
83 changes: 34 additions & 49 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@
"ts-pattern": "^5.0.6",
"uuid": "^9.0.1",
"vaul": "^0.8.0",
"zod": "^3.22.4"
"zod": "^3.22.4",
"prisma": "^5.7.0"
},
"devDependencies": {
"@total-typescript/ts-reset": "^0.5.1",
Expand All @@ -70,7 +71,6 @@
"postcss": "^8",
"prettier": "^3.0.3",
"prettier-plugin-organize-imports": "^3.2.3",
"prisma": "^5.7.0",
"tailwindcss": "^3",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.3.3"
Expand Down
3 changes: 0 additions & 3 deletions scripts/build-image.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@ SPLIIT_VERSION=$(node -p -e "require('./package.json').version")

# we need to set dummy data for POSTGRES env vars in order for build not to fail
docker buildx build \
--no-cache \
--build-arg POSTGRES_PRISMA_URL=postgresql://build:@db \
--build-arg POSTGRES_URL_NON_POOLING=postgresql://build:@db \
-t ${SPLIIT_APP_NAME}:${SPLIIT_VERSION} \
-t ${SPLIIT_APP_NAME}:latest \
.
Expand Down
22 changes: 22 additions & 0 deletions scripts/build.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# build file that contains all possible env vars with mocked values
# as most of them are used at build time in order to have the production build to work properly

# db
POSTGRES_PASSWORD=1234

# app
POSTGRES_PRISMA_URL=postgresql://postgres:${POSTGRES_PASSWORD}@db
POSTGRES_URL_NON_POOLING=postgresql://postgres:${POSTGRES_PASSWORD}@db

# app-minio
NEXT_PUBLIC_ENABLE_EXPENSE_DOCUMENTS=false
S3_UPLOAD_KEY=AAAAAAAAAAAAAAAAAAAA
S3_UPLOAD_SECRET=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
S3_UPLOAD_BUCKET=spliit
S3_UPLOAD_REGION=eu-north-1
S3_UPLOAD_ENDPOINT=s3://minio.example.com

# app-openai
NEXT_PUBLIC_ENABLE_RECEIPT_EXTRACT=false
OPENAI_API_KEY=XXXXXXXXXXXXXXXXXXXXXXXXXXXX
NEXT_PUBLIC_ENABLE_CATEGORY_EXTRACT=false
5 changes: 4 additions & 1 deletion scripts/container-entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#!/bin/bash
prisma migrate deploy

set -euxo pipefail

npx prisma migrate deploy
npm run start
2 changes: 2 additions & 0 deletions src/app/groups/[groupId]/expenses/[expenseId]/edit/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
getExpense,
updateExpense,
} from '@/lib/api'
import { getRuntimeFeatureFlags } from '@/lib/featureFlags'
import { expenseFormSchema } from '@/lib/schemas'
import { Metadata } from 'next'
import { notFound, redirect } from 'next/navigation'
Expand Down Expand Up @@ -47,6 +48,7 @@ export default async function EditExpensePage({
categories={categories}
onSubmit={updateExpenseAction}
onDelete={deleteExpenseAction}
runtimeFeatureFlags={await getRuntimeFeatureFlags()}
/>
</Suspense>
)
Expand Down
2 changes: 2 additions & 0 deletions src/app/groups/[groupId]/expenses/create/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { cached } from '@/app/cached-functions'
import { ExpenseForm } from '@/components/expense-form'
import { createExpense, getCategories } from '@/lib/api'
import { getRuntimeFeatureFlags } from '@/lib/featureFlags'
import { expenseFormSchema } from '@/lib/schemas'
import { Metadata } from 'next'
import { notFound, redirect } from 'next/navigation'
Expand Down Expand Up @@ -32,6 +33,7 @@ export default async function ExpensePage({
group={group}
categories={categories}
onSubmit={createExpenseAction}
runtimeFeatureFlags={await getRuntimeFeatureFlags()}
/>
</Suspense>
)
Expand Down
7 changes: 5 additions & 2 deletions src/components/expense-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
SelectValue,
} from '@/components/ui/select'
import { getCategories, getExpense, getGroup, randomId } from '@/lib/api'
import { RuntimeFeatureFlags } from '@/lib/featureFlags'
import { ExpenseFormValues, expenseFormSchema } from '@/lib/schemas'
import { cn } from '@/lib/utils'
import { zodResolver } from '@hookform/resolvers/zod'
Expand All @@ -52,6 +53,7 @@ export type Props = {
categories: NonNullable<Awaited<ReturnType<typeof getCategories>>>
onSubmit: (values: ExpenseFormValues) => Promise<void>
onDelete?: () => Promise<void>
runtimeFeatureFlags: RuntimeFeatureFlags
}

export function ExpenseForm({
Expand All @@ -60,6 +62,7 @@ export function ExpenseForm({
categories,
onSubmit,
onDelete,
runtimeFeatureFlags,
}: Props) {
const isCreate = expense === undefined
const searchParams = useSearchParams()
Expand Down Expand Up @@ -161,7 +164,7 @@ export function ExpenseForm({
{...field}
onBlur={async () => {
field.onBlur() // avoid skipping other blur event listeners since we overwrite `field`
if (process.env.NEXT_PUBLIC_ENABLE_CATEGORY_EXTRACT) {
if (runtimeFeatureFlags.enableCategoryExtract) {
setCategoryLoading(true)
const { categoryId } = await extractCategoryFromTitle(
field.value,
Expand Down Expand Up @@ -541,7 +544,7 @@ export function ExpenseForm({
</CardContent>
</Card>

{process.env.NEXT_PUBLIC_ENABLE_EXPENSE_DOCUMENTS && (
{runtimeFeatureFlags.enableExpenseDocuments && (
<Card className="mt-4">
<CardHeader>
<CardTitle className="flex justify-between">
Expand Down
20 changes: 17 additions & 3 deletions src/lib/env.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { ZodIssueCode, z } from 'zod'

const interpretEnvVarAsBool = (val: unknown): boolean => {
if (typeof val !== 'string') return false
return ['true', 'yes', '1', 'on'].includes(val.toLowerCase())
}

const envSchema = z
.object({
POSTGRES_URL_NON_POOLING: z.string().url(),
Expand All @@ -12,14 +17,23 @@ const envSchema = z
? `https://${process.env.VERCEL_URL}`
: 'http://localhost:3000',
),
NEXT_PUBLIC_ENABLE_EXPENSE_DOCUMENTS: z.coerce.boolean().default(false),
NEXT_PUBLIC_ENABLE_EXPENSE_DOCUMENTS: z.preprocess(
interpretEnvVarAsBool,
z.boolean().default(false),
),
S3_UPLOAD_KEY: z.string().optional(),
S3_UPLOAD_SECRET: z.string().optional(),
S3_UPLOAD_BUCKET: z.string().optional(),
S3_UPLOAD_REGION: z.string().optional(),
S3_UPLOAD_ENDPOINT: z.string().optional(),
NEXT_PUBLIC_ENABLE_RECEIPT_EXTRACT: z.coerce.boolean().default(false),
NEXT_PUBLIC_ENABLE_CATEGORY_EXTRACT: z.coerce.boolean().default(false),
NEXT_PUBLIC_ENABLE_RECEIPT_EXTRACT: z.preprocess(
interpretEnvVarAsBool,
z.boolean().default(false),
),
NEXT_PUBLIC_ENABLE_CATEGORY_EXTRACT: z.preprocess(
interpretEnvVarAsBool,
z.boolean().default(false),
),
OPENAI_API_KEY: z.string().optional(),
})
.superRefine((env, ctx) => {
Expand Down
15 changes: 15 additions & 0 deletions src/lib/featureFlags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use server'

import { env } from './env'

export async function getRuntimeFeatureFlags() {
return {
enableExpenseDocuments: env.NEXT_PUBLIC_ENABLE_EXPENSE_DOCUMENTS,
enableReceiptExtract: env.NEXT_PUBLIC_ENABLE_RECEIPT_EXTRACT,
enableCategoryExtract: env.NEXT_PUBLIC_ENABLE_CATEGORY_EXTRACT,
}
}

export type RuntimeFeatureFlags = Awaited<
ReturnType<typeof getRuntimeFeatureFlags>
>

0 comments on commit 2af0660

Please sign in to comment.