Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize docker image size #91

Merged
merged 13 commits into from
Feb 14, 2024
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
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 @@ -51,6 +52,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 @@ -59,6 +61,7 @@ export function ExpenseForm({
categories,
onSubmit,
onDelete,
runtimeFeatureFlags,
}: Props) {
const isCreate = expense === undefined
const searchParams = useSearchParams()
Expand Down Expand Up @@ -160,7 +163,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 @@ -540,7 +543,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>
>
Loading