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

Speed up Docker image builds using multi-stage parallel pipelines #987

Merged
merged 7 commits into from
Nov 25, 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
13 changes: 7 additions & 6 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
.git/
.pytest_cache/
.vscode/
.venv/
docs/
.*
**/__pycache__/
*.egg-info/
documentation/
tests/
build/
dist/
scripts/
*.egg-info/
src/interface/
src/telemetry/
!src/interface/web
8 changes: 6 additions & 2 deletions .github/workflows/dockerize.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ jobs:
run: rm -rf /opt/hostedtoolcache

- name: 📦 Build and Push Docker Image
uses: docker/build-push-action@v2
uses: docker/build-push-action@v4
if: (matrix.image == 'local' && github.event_name == 'workflow_dispatch') && github.event.inputs.khoj == 'true' || (matrix.image == 'local' && github.event_name == 'push')
with:
context: .
Expand All @@ -86,9 +86,11 @@ jobs:
build-args: |
VERSION=${{ steps.hatch.outputs.version }}
PORT=42110
cache-from: type=gha,scope=${{ matrix.image }}
cache-to: type=gha,mode=max,scope=${{ matrix.image }}

- name: 📦️⛅️ Build and Push Cloud Docker Image
uses: docker/build-push-action@v2
uses: docker/build-push-action@v4
if: (matrix.image == 'cloud' && github.event_name == 'workflow_dispatch') && github.event.inputs.khoj-cloud == 'true' || (matrix.image == 'cloud' && github.event_name == 'push')
with:
context: .
Expand All @@ -101,3 +103,5 @@ jobs:
build-args: |
VERSION=${{ steps.hatch.outputs.version }}
PORT=42110
cache-from: type=gha,scope=${{ matrix.image }}
cache-to: type=gha,mode=max,scope=${{ matrix.image }}
54 changes: 32 additions & 22 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# syntax=docker/dockerfile:1
FROM ubuntu:jammy
FROM ubuntu:jammy AS base
LABEL homepage="https://khoj.dev"
LABEL repository="https://github.com/khoj-ai/khoj"
LABEL org.opencontainers.image.source="https://github.com/khoj-ai/khoj"
Expand All @@ -10,44 +10,54 @@ RUN apt update -y && apt -y install \
python3-pip \
swig \
curl \
# Required by llama-cpp-python pre-built wheels. See #1628
musl-dev \
# Required by RapidOCR
libgl1 \
libglx-mesa0 \
libglib2.0-0 && \
# Required by Next.js Web app
curl -sL https://deb.nodesource.com/setup_20.x | bash - && \
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
apt update -y && apt -y --no-install-recommends install nodejs yarn && \
apt clean && rm -rf /var/lib/apt/lists/* && \
libglib2.0-0 \
# Required by llama-cpp-python pre-built wheels. See #1628
ln -s /usr/lib/x86_64-linux-musl/libc.so /lib/libc.musl-x86_64.so.1
musl-dev && \
ln -s /usr/lib/x86_64-linux-musl/libc.so /lib/libc.musl-x86_64.so.1 && \
# Clean up
apt clean && rm -rf /var/lib/apt/lists/*

# Install Application
# Build Server
FROM base AS server-deps
WORKDIR /app
COPY pyproject.toml .
COPY README.md .
ARG VERSION=0.0.0
ENV PIP_EXTRA_INDEX_URL=https://abetlen.github.io/llama-cpp-python/whl/cpu
# use the pre-built llama-cpp-python, torch cpu wheel
ENV PIP_EXTRA_INDEX_URL="https://download.pytorch.org/whl/cpu https://abetlen.github.io/llama-cpp-python/whl/cpu"
# avoid downloading unused cuda specific python packages
ENV CUDA_VISIBLE_DEVICES=""
RUN sed -i "s/dynamic = \\[\"version\"\\]/version = \"$VERSION\"/" pyproject.toml && \
pip install --no-cache-dir .

# Copy Source Code
COPY . .

# Set the PYTHONPATH environment variable in order for it to find the Django app.
ENV PYTHONPATH=/app/src:$PYTHONPATH

# Go to the directory src/interface/web and export the built Next.js assets
# Build Web App
FROM node:20-alpine AS web-app
# Set build optimization env vars
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
WORKDIR /app/src/interface/web
RUN bash -c "yarn install --frozen-lockfile && yarn ciexport && yarn cache clean"
# Install dependencies first (cache layer)
COPY src/interface/web/package.json src/interface/web/yarn.lock ./
RUN yarn install --frozen-lockfile
# Copy source and build
COPY src/interface/web/. ./
RUN yarn build

# Merge the Server and Web App into a Single Image
FROM base
ENV PYTHONPATH=/app/src
WORKDIR /app
COPY --from=server-deps /usr/local/lib/python3.10/dist-packages /usr/local/lib/python3.10/dist-packages
COPY --from=web-app /app/src/interface/web/out ./src/khoj/interface/built
COPY . .
RUN cd src && python3 khoj/manage.py collectstatic --noinput

# Run the Application
# There are more arguments required for the application to run,
# but these should be passed in through the docker-compose.yml file.
# but those should be passed in through the docker-compose.yml file.
ARG PORT
EXPOSE ${PORT}
ENTRYPOINT ["python3", "src/khoj/main.py"]
52 changes: 31 additions & 21 deletions prod.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# syntax=docker/dockerfile:1
FROM ubuntu:jammy
FROM ubuntu:jammy AS base
LABEL homepage="https://khoj.dev"
LABEL repository="https://github.com/khoj-ai/khoj"
LABEL org.opencontainers.image.source="https://github.com/khoj-ai/khoj"
Expand All @@ -16,38 +16,48 @@ RUN apt update -y && apt -y install \
curl \
# Required by llama-cpp-python pre-built wheels. See #1628
musl-dev && \
# Required by Next.js Web app
curl -sL https://deb.nodesource.com/setup_20.x | bash - && \
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
apt update -y && apt -y --no-install-recommends install nodejs yarn && \
apt clean && rm -rf /var/lib/apt/lists/* && \
# Required by llama-cpp-python pre-built wheels. See #1628
ln -s /usr/lib/x86_64-linux-musl/libc.so /lib/libc.musl-x86_64.so.1
ln -s /usr/lib/x86_64-linux-musl/libc.so /lib/libc.musl-x86_64.so.1 && \
# Clean up
apt clean && rm -rf /var/lib/apt/lists/*

# Install Application
# Build Server
FROM base AS server-deps
WORKDIR /app
COPY pyproject.toml .
COPY README.md .
ARG VERSION=0.0.0
ENV PIP_EXTRA_INDEX_URL=https://abetlen.github.io/llama-cpp-python/whl/cpu
# use the pre-built llama-cpp-python, torch cpu wheel
ENV PIP_EXTRA_INDEX_URL="https://download.pytorch.org/whl/cpu https://abetlen.github.io/llama-cpp-python/whl/cpu"
# avoid downloading unused cuda specific python packages
ENV CUDA_VISIBLE_DEVICES=""
RUN sed -i "s/dynamic = \\[\"version\"\\]/version = \"$VERSION\"/" pyproject.toml && \
pip install --no-cache-dir -e .[prod]

# Copy Source Code
COPY . .
pip install --no-cache-dir .[prod]

# Set the PYTHONPATH environment variable in order for it to find the Django app.
ENV PYTHONPATH=/app/src:$PYTHONPATH

# Go to the directory src/interface/web and export the built Next.js assets
# Build Web App
FROM node:20-alpine AS web-app
# Set build optimization env vars
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
WORKDIR /app/src/interface/web
RUN bash -c "yarn install --frozen-lockfile && yarn ciexport && yarn cache clean"
# Install dependencies first (cache layer)
COPY src/interface/web/package.json src/interface/web/yarn.lock ./
RUN yarn install --frozen-lockfile
# Copy source and build
COPY src/interface/web/. ./
RUN yarn build

# Merge the Server and Web App into a Single Image
FROM base
ENV PYTHONPATH=/app/src
WORKDIR /app
COPY --from=server-deps /usr/local/lib/python3.10/dist-packages /usr/local/lib/python3.10/dist-packages
COPY --from=web-app /app/src/interface/web/out ./src/khoj/interface/built
COPY . .
RUN cd src && python3 khoj/manage.py collectstatic --noinput

# Run the Application
# There are more arguments required for the application to run,
# but these should be passed in through the docker-compose.yml file.
# but those should be passed in through the docker-compose.yml file.
ARG PORT
EXPOSE ${PORT}
ENTRYPOINT ["gunicorn", "-c", "gunicorn-config.py", "src.khoj.main:app"]
8 changes: 3 additions & 5 deletions src/interface/web/app/agents/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import type { Metadata } from "next";
import { Noto_Sans } from "next/font/google";
import { noto_sans, noto_sans_arabic } from "@/app/fonts";
import "../globals.css";

const inter = Noto_Sans({ subsets: ["latin"] });

export const metadata: Metadata = {
title: "Khoj AI - Agents",
description: "Find a specialized agent that can help you address more specific needs.",
Expand Down Expand Up @@ -33,7 +31,7 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html lang="en">
<html lang="en" className={`${noto_sans.variable} ${noto_sans_arabic.variable}`}>
<meta
httpEquiv="Content-Security-Policy"
content="default-src 'self' https://assets.khoj.dev;
Expand All @@ -46,7 +44,7 @@ export default function RootLayout({
child-src 'none';
object-src 'none';"
></meta>
<body className={inter.className}>{children}</body>
<body>{children}</body>
</html>
);
}
8 changes: 3 additions & 5 deletions src/interface/web/app/chat/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import type { Metadata } from "next";
import { Noto_Sans } from "next/font/google";
import { noto_sans, noto_sans_arabic } from "@/app/fonts";
import "../globals.css";

const inter = Noto_Sans({ subsets: ["latin"] });

export const metadata: Metadata = {
title: "Khoj AI - Chat",
description:
Expand Down Expand Up @@ -34,7 +32,7 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html lang="en">
<html lang="en" className={`${noto_sans.variable} ${noto_sans_arabic.variable}`}>
<meta
httpEquiv="Content-Security-Policy"
content="default-src 'self' https://assets.khoj.dev;
Expand All @@ -47,7 +45,7 @@ export default function RootLayout({
child-src 'none';
object-src 'none';"
></meta>
<body className={inter.className}>
<body>
{children}
<script
dangerouslySetInnerHTML={{
Expand Down
13 changes: 13 additions & 0 deletions src/interface/web/app/fonts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Noto_Sans, Noto_Sans_Arabic } from "next/font/google";

export const noto_sans = Noto_Sans({
subsets: ["latin", "latin-ext", "cyrillic", "cyrillic-ext", "devanagari", "vietnamese"],
display: "swap",
variable: "--font-noto-sans",
});

export const noto_sans_arabic = Noto_Sans_Arabic({
subsets: ["arabic"],
display: "swap",
variable: "--font-noto-sans-arabic",
});
5 changes: 2 additions & 3 deletions src/interface/web/app/globals.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import url("https://fonts.googleapis.com/css2?family=Noto+Sans+Arabic:[email protected]&family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap");

@layer base {
:root {
Expand All @@ -25,7 +24,7 @@
--input: 220 13% 91%;
--ring: 24.6 95% 53.1%;
--radius: 0.5rem;
--font-family: "Noto Sans", "Noto Sans Arabic", sans-serif !important;
--font-family: var(--font-noto-sans), var(--font-noto-sans-arabic), sans-serif !important;

/* Khoj Custom Colors */
--frosted-background-color: 20 13% 95%;
Expand Down Expand Up @@ -188,7 +187,7 @@
--border: 0 0% 9%;
--input: 0 0% 9%;
--ring: 20.5 90.2% 48.2%;
--font-family: "Noto Sans", "Noto Sans Arabic", sans-serif !important;
--font-family: var(--font-noto-sans), var(--font-noto-sans-arabic), sans-serif !important;

/* Imported from highlight.js */
pre code.hljs {
Expand Down
8 changes: 3 additions & 5 deletions src/interface/web/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import type { Metadata } from "next";
import { Noto_Sans } from "next/font/google";
import { noto_sans, noto_sans_arabic } from "@/app/fonts";
import "./globals.css";

const inter = Noto_Sans({ subsets: ["latin"] });

export const metadata: Metadata = {
title: "Khoj AI - Home",
description: "Your Second Brain.",
Expand Down Expand Up @@ -39,7 +37,7 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html lang="en">
<html lang="en" className={`${noto_sans.variable} ${noto_sans_arabic.variable}`}>
<meta
httpEquiv="Content-Security-Policy"
content="default-src 'self' https://assets.khoj.dev;
Expand All @@ -52,7 +50,7 @@ export default function RootLayout({
child-src 'none';
object-src 'none';"
></meta>
<body className={inter.className}>{children}</body>
<body>{children}</body>
</html>
);
}
8 changes: 3 additions & 5 deletions src/interface/web/app/settings/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import type { Metadata } from "next";
import { Noto_Sans } from "next/font/google";
import { noto_sans, noto_sans_arabic } from "@/app/fonts";
import "../globals.css";
import { Toaster } from "@/components/ui/toaster";

const inter = Noto_Sans({ subsets: ["latin"] });

export const metadata: Metadata = {
title: "Khoj AI - Settings",
description: "Configure Khoj to get personalized, deeper assistance.",
Expand Down Expand Up @@ -34,7 +32,7 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html lang="en">
<html lang="en" className={`${noto_sans.variable} ${noto_sans_arabic.variable}`}>
<meta
httpEquiv="Content-Security-Policy"
content="default-src 'self' https://assets.khoj.dev;
Expand All @@ -46,7 +44,7 @@ export default function RootLayout({
child-src 'none';
object-src 'none';"
></meta>
<body className={inter.className}>
<body>
{children}
<Toaster />
</body>
Expand Down
8 changes: 3 additions & 5 deletions src/interface/web/app/share/chat/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import type { Metadata } from "next";
import { Noto_Sans } from "next/font/google";
import { noto_sans, noto_sans_arabic } from "@/app/fonts";
import "../../globals.css";

const inter = Noto_Sans({ subsets: ["latin"] });

export const metadata: Metadata = {
title: "Khoj AI - Chat",
description: "Use this page to view a chat with Khoj AI.",
Expand All @@ -15,7 +13,7 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html lang="en">
<html lang="en" className={`${noto_sans.variable} ${noto_sans_arabic.variable}`}>
<meta
httpEquiv="Content-Security-Policy"
content="default-src 'self' https://assets.khoj.dev;
Expand All @@ -27,7 +25,7 @@ export default function RootLayout({
child-src 'none';
object-src 'none';"
></meta>
<body className={inter.className}>
<body>
{children}
<script
dangerouslySetInnerHTML={{
Expand Down
Loading
Loading