mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-07-09 03:04:54 -04:00
refactor/docker-updates (#369)
* convert all images to webp * consolidate docker files * serve images wiith caddy * consolidate docker files * new slim-buster image * set image url * add image path * remove print * set image path correctly * cleanup * caddy proxy path * docs Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
parent
7153ff6f25
commit
3e80947a4c
@ -5,11 +5,16 @@
|
|||||||
|
|
||||||
:80 {
|
:80 {
|
||||||
@proxied path /api/* /docs /openapi.json
|
@proxied path /api/* /docs /openapi.json
|
||||||
|
|
||||||
root * /app/dist
|
root * /app/dist
|
||||||
encode gzip
|
encode gzip
|
||||||
uri strip_suffix /
|
uri strip_suffix /
|
||||||
|
|
||||||
|
handle_path /api/recipes/image/* {
|
||||||
|
root * /app/data/img/
|
||||||
|
file_server
|
||||||
|
}
|
||||||
|
|
||||||
handle @proxied {
|
handle @proxied {
|
||||||
reverse_proxy http://127.0.0.1:9000
|
reverse_proxy http://127.0.0.1:9000
|
||||||
}
|
}
|
||||||
|
66
Dockerfile
66
Dockerfile
@ -1,3 +1,4 @@
|
|||||||
|
# build
|
||||||
FROM node:lts-alpine as build-stage
|
FROM node:lts-alpine as build-stage
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY ./frontend/package*.json ./
|
COPY ./frontend/package*.json ./
|
||||||
@ -5,50 +6,53 @@ RUN npm install
|
|||||||
COPY ./frontend/ .
|
COPY ./frontend/ .
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
FROM python:3.9-alpine
|
|
||||||
|
|
||||||
|
|
||||||
RUN apk add --no-cache libxml2-dev \
|
|
||||||
libxslt-dev \
|
|
||||||
libxml2 caddy \
|
|
||||||
libffi-dev \
|
|
||||||
python3 \
|
|
||||||
python3-dev \
|
|
||||||
jpeg-dev \
|
|
||||||
lcms2-dev \
|
|
||||||
openjpeg-dev \
|
|
||||||
zlib-dev
|
|
||||||
|
|
||||||
|
FROM python:3.9-slim-buster
|
||||||
|
|
||||||
ENV PRODUCTION true
|
ENV PRODUCTION true
|
||||||
EXPOSE 80
|
ENV POETRY_VERSION 1.1.6
|
||||||
WORKDIR /app/
|
|
||||||
|
|
||||||
COPY ./pyproject.toml /app/
|
|
||||||
|
|
||||||
RUN apk add --update --no-cache --virtual .build-deps \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
gcc g++ \
|
||||||
curl \
|
curl \
|
||||||
g++ \
|
gnupg gnupg2 gnupg1 \
|
||||||
python3-dev \
|
apt-transport-https \
|
||||||
musl-dev \
|
debian-archive-keyring \
|
||||||
gcc \
|
debian-keyring \
|
||||||
build-base && \
|
libwebp-dev \
|
||||||
curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | POETRY_HOME=/opt/poetry python && \
|
&& curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | apt-key add - \
|
||||||
cd /usr/local/bin && \
|
&& curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee -a /etc/apt/sources.list.d/caddy-stable.list \
|
||||||
ln -s /opt/poetry/bin/poetry && \
|
&& apt-get update && apt-get install -y --no-install-recommends \
|
||||||
poetry config virtualenvs.create false && \
|
caddy \
|
||||||
cd /app/ && poetry install --no-root --no-dev && \
|
&& apt autoremove \
|
||||||
apk --purge del .build-deps
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
|
&& apt-get remove -y curl apt-transport-https debian-keyring g++ gnupg gnupg2 gnupg1
|
||||||
|
|
||||||
|
|
||||||
|
RUN pip install --no-cache-dir "poetry==$POETRY_VERSION"
|
||||||
|
|
||||||
|
#! Future
|
||||||
|
# pip install --no-cache-dir "psycopg2-binary==2.8.6"
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY pyproject.toml /app/
|
||||||
COPY ./mealie /app/mealie
|
COPY ./mealie /app/mealie
|
||||||
RUN poetry install --no-dev
|
RUN poetry config virtualenvs.create false \
|
||||||
|
&& poetry install --no-dev
|
||||||
|
|
||||||
|
#! Future
|
||||||
|
# COPY ./alembic /app
|
||||||
|
# COPY alembic.ini /app
|
||||||
|
|
||||||
COPY ./Caddyfile /app
|
COPY ./Caddyfile /app
|
||||||
COPY ./dev/data/templates /app/data/templates
|
COPY ./dev/data/templates /app/data/templates
|
||||||
|
|
||||||
|
# frontend build
|
||||||
COPY --from=build-stage /app/dist /app/dist
|
COPY --from=build-stage /app/dist /app/dist
|
||||||
|
|
||||||
VOLUME [ "/app/data/" ]
|
VOLUME [ "/app/data/" ]
|
||||||
|
|
||||||
RUN chmod +x /app/mealie/run.sh
|
EXPOSE 80
|
||||||
CMD /app/mealie/run.sh
|
|
||||||
|
|
||||||
|
CMD /app/mealie/run.sh
|
@ -1,24 +1,33 @@
|
|||||||
FROM python:3
|
FROM python:3.9-slim-buster
|
||||||
|
|
||||||
|
|
||||||
|
ENV PRODUCTION false
|
||||||
|
ENV POETRY_VERSION 1.1.6
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
gcc g++ \
|
||||||
|
curl \
|
||||||
|
gnupg gnupg2 gnupg1 \
|
||||||
|
apt-transport-https \
|
||||||
|
debian-archive-keyring \
|
||||||
|
debian-keyring \
|
||||||
|
libwebp-dev \
|
||||||
|
&& curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | apt-key add - \
|
||||||
|
&& curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee -a /etc/apt/sources.list.d/caddy-stable.list \
|
||||||
|
&& apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
&& apt autoremove \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
|
&& apt-get remove -y curl apt-transport-https debian-keyring g++ gnupg gnupg2 gnupg1
|
||||||
|
|
||||||
|
RUN pip install --no-cache-dir "poetry==$POETRY_VERSION"
|
||||||
|
|
||||||
WORKDIR /app/
|
WORKDIR /app/
|
||||||
|
|
||||||
ENV PRODUCTION false
|
|
||||||
|
|
||||||
RUN apt-get update -y && \
|
|
||||||
apt-get install -y python-pip python-dev
|
|
||||||
|
|
||||||
# Install Poetry
|
|
||||||
RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | POETRY_HOME=/opt/poetry python && \
|
|
||||||
cd /usr/local/bin && \
|
|
||||||
ln -s /opt/poetry/bin/poetry && \
|
|
||||||
poetry config virtualenvs.create false
|
|
||||||
|
|
||||||
# Copy poetry.lock* in case it doesn't exist in the repo
|
# Copy poetry.lock* in case it doesn't exist in the repo
|
||||||
COPY ./pyproject.toml /app/
|
COPY ./pyproject.toml /app/
|
||||||
|
|
||||||
COPY ./mealie /app/mealie
|
COPY ./mealie /app/mealie
|
||||||
|
RUN poetry config virtualenvs.create false \
|
||||||
RUN poetry install
|
&& poetry install
|
||||||
|
|
||||||
RUN chmod +x /app/mealie/run.sh
|
RUN chmod +x /app/mealie/run.sh
|
||||||
CMD ["/app/mealie/run.sh", "reload"]
|
CMD ["/app/mealie/run.sh", "reload"]
|
||||||
|
@ -3,6 +3,7 @@ version: "3.1"
|
|||||||
services:
|
services:
|
||||||
# Vue Frontend
|
# Vue Frontend
|
||||||
mealie-frontend:
|
mealie-frontend:
|
||||||
|
container_name: mealie-frontend
|
||||||
image: mealie-frontend:dev
|
image: mealie-frontend:dev
|
||||||
build:
|
build:
|
||||||
context: ./frontend
|
context: ./frontend
|
||||||
@ -18,6 +19,7 @@ services:
|
|||||||
|
|
||||||
# Fast API
|
# Fast API
|
||||||
mealie-api:
|
mealie-api:
|
||||||
|
container_name: mealie-api
|
||||||
image: mealie-api:dev
|
image: mealie-api:dev
|
||||||
build:
|
build:
|
||||||
context: ./
|
context: ./
|
||||||
@ -34,6 +36,7 @@ services:
|
|||||||
|
|
||||||
# Mkdocs
|
# Mkdocs
|
||||||
mealie-docs:
|
mealie-docs:
|
||||||
|
container_name: mealie-docs
|
||||||
image: squidfunk/mkdocs-material
|
image: squidfunk/mkdocs-material
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
|
@ -20,6 +20,11 @@
|
|||||||
- 'Dinner this week' shows a warning when no meal is planned yet
|
- 'Dinner this week' shows a warning when no meal is planned yet
|
||||||
- 'Dinner today' shows a warning when no meal is planned yet
|
- 'Dinner today' shows a warning when no meal is planned yet
|
||||||
|
|
||||||
|
### Performance
|
||||||
|
- Images are now served up by the Caddy increase performance and offloading some loads from the API server
|
||||||
|
- Requesting all recipes from the server has been rewritten to refresh less often and manage client side data better.
|
||||||
|
- All images are now converted to .webp for better compression
|
||||||
|
|
||||||
### General
|
### General
|
||||||
- New Toolbox Page!
|
- New Toolbox Page!
|
||||||
- Bulk assign categories and tags by keyword search
|
- Bulk assign categories and tags by keyword search
|
||||||
@ -38,6 +43,7 @@
|
|||||||
|
|
||||||
|
|
||||||
### Behind the Scenes
|
### Behind the Scenes
|
||||||
|
- New debian based docker image
|
||||||
- Unified Sidebar Components
|
- Unified Sidebar Components
|
||||||
- Refactor UI components to fit Vue best practices (WIP)
|
- Refactor UI components to fit Vue best practices (WIP)
|
||||||
- The API returns more consistent status codes
|
- The API returns more consistent status codes
|
||||||
|
@ -134,14 +134,14 @@ export const recipeAPI = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
recipeImage(recipeSlug) {
|
recipeImage(recipeSlug) {
|
||||||
return `/api/recipes/${recipeSlug}/image?image_type=original`;
|
return `/api/recipes/image/${recipeSlug}/original.webp`;
|
||||||
},
|
},
|
||||||
|
|
||||||
recipeSmallImage(recipeSlug) {
|
recipeSmallImage(recipeSlug) {
|
||||||
return `/api/recipes/${recipeSlug}/image?image_type=small`;
|
return `/api/recipes/image/${recipeSlug}/min-original.webp`;
|
||||||
},
|
},
|
||||||
|
|
||||||
recipeTinyImage(recipeSlug) {
|
recipeTinyImage(recipeSlug) {
|
||||||
return `/api/recipes/${recipeSlug}/image?image_type=tiny`;
|
return `/api/recipes/image/${recipeSlug}/tiny-original.webp`;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -165,7 +165,7 @@ export default {
|
|||||||
},
|
},
|
||||||
getImage(image) {
|
getImage(image) {
|
||||||
if (image) {
|
if (image) {
|
||||||
return api.recipes.recipeImage(image) + "&rnd=" + this.imageKey;
|
return api.recipes.recipeImage(image) + "?&rnd=" + this.imageKey;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async deleteRecipe() {
|
async deleteRecipe() {
|
||||||
|
2
makefile
2
makefile
@ -74,7 +74,7 @@ docker-dev: ## Build and Start Docker Development Stack
|
|||||||
docker-compose -f docker-compose.dev.yml -p dev-mealie up --build
|
docker-compose -f docker-compose.dev.yml -p dev-mealie up --build
|
||||||
|
|
||||||
docker-prod: ## Build and Start Docker Production Stack
|
docker-prod: ## Build and Start Docker Production Stack
|
||||||
docker-compose -p mealie up --build -d
|
docker-compose -f docker-compose.yml -p mealie up --build -d
|
||||||
|
|
||||||
code-gen: ## Run Code-Gen Scripts
|
code-gen: ## Run Code-Gen Scripts
|
||||||
poetry run python dev/scripts/app_routes_gen.py
|
poetry run python dev/scripts/app_routes_gen.py
|
||||||
|
@ -118,7 +118,6 @@ class RecipeModel(SqlAlchemyBase, BaseMixins):
|
|||||||
|
|
||||||
# Mealie Specific
|
# Mealie Specific
|
||||||
self.settings = RecipeSettings(**settings) if settings else RecipeSettings()
|
self.settings = RecipeSettings(**settings) if settings else RecipeSettings()
|
||||||
print(self.settings)
|
|
||||||
self.tags = [Tag.create_if_not_exist(session=session, name=tag) for tag in tags]
|
self.tags = [Tag.create_if_not_exist(session=session, name=tag) for tag in tags]
|
||||||
self.slug = slug
|
self.slug = slug
|
||||||
self.date_added = date_added
|
self.date_added = date_added
|
||||||
|
@ -2,6 +2,8 @@ from enum import Enum
|
|||||||
|
|
||||||
from fastapi import APIRouter, Depends, File, Form, HTTPException, status
|
from fastapi import APIRouter, Depends, File, Form, HTTPException, status
|
||||||
from fastapi.responses import FileResponse
|
from fastapi.responses import FileResponse
|
||||||
|
from mealie.core.config import app_dirs
|
||||||
|
from mealie.core.root_logger import get_logger
|
||||||
from mealie.db.database import db
|
from mealie.db.database import db
|
||||||
from mealie.db.db_setup import generate_session
|
from mealie.db.db_setup import generate_session
|
||||||
from mealie.routes.deps import get_current_user
|
from mealie.routes.deps import get_current_user
|
||||||
@ -11,6 +13,7 @@ from mealie.services.scraper.scraper import create_from_url
|
|||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/recipes", tags=["Recipe CRUD"])
|
router = APIRouter(prefix="/api/recipes", tags=["Recipe CRUD"])
|
||||||
|
logger = get_logger()
|
||||||
|
|
||||||
|
|
||||||
@router.post("/create", status_code=201, response_model=str)
|
@router.post("/create", status_code=201, response_model=str)
|
||||||
@ -104,22 +107,15 @@ def delete_recipe(
|
|||||||
|
|
||||||
|
|
||||||
class ImageType(str, Enum):
|
class ImageType(str, Enum):
|
||||||
original = "original"
|
original = "original.webp"
|
||||||
small = "small"
|
small = "min-original.webp"
|
||||||
tiny = "tiny"
|
tiny = "tiny-original.webp"
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{recipe_slug}/image")
|
@router.get("/image/{recipe_slug}/{file_name}")
|
||||||
async def get_recipe_img(recipe_slug: str, image_type: ImageType = ImageType.original):
|
async def get_recipe_img(recipe_slug: str, file_name: ImageType = ImageType.original):
|
||||||
""" Takes in a recipe slug, returns the static image """
|
""" Takes in a recipe slug, returns the static image """
|
||||||
if image_type == ImageType.original:
|
recipe_image = app_dirs.IMG_DIR.joinpath(recipe_slug, file_name.value)
|
||||||
which_image = IMG_OPTIONS.ORIGINAL_IMAGE
|
|
||||||
elif image_type == ImageType.small:
|
|
||||||
which_image = IMG_OPTIONS.MINIFIED_IMAGE
|
|
||||||
elif image_type == ImageType.tiny:
|
|
||||||
which_image = IMG_OPTIONS.TINY_IMAGE
|
|
||||||
|
|
||||||
recipe_image = read_image(recipe_slug, image_type=which_image)
|
|
||||||
if recipe_image:
|
if recipe_image:
|
||||||
return FileResponse(recipe_image)
|
return FileResponse(recipe_image)
|
||||||
else:
|
else:
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
#!/bin/sh
|
#!/bin/bash
|
||||||
|
|
||||||
# Get Reload Arg `run.sh reload` for dev server
|
# Get Reload Arg `run.sh reload` for dev server
|
||||||
ARG1=${1:-production}
|
ARG1=${1:-production}
|
||||||
|
|
||||||
# Set Script Directory - Used for running the script from a different directory.
|
|
||||||
# DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
|
||||||
|
|
||||||
# # Initialize Database Prerun
|
# # Initialize Database Prerun
|
||||||
poetry run python /app/mealie/db/init_db.py
|
poetry run python /app/mealie/db/init_db.py
|
||||||
poetry run python /app/mealie/services/image/minify.py
|
poetry run python /app/mealie/services/image/minify.py
|
||||||
@ -15,12 +12,12 @@ poetry run python /app/mealie/services/image/minify.py
|
|||||||
# Migrations
|
# Migrations
|
||||||
# Set Port from ENV Variable
|
# Set Port from ENV Variable
|
||||||
|
|
||||||
if [[ "$ARG1" = "reload" ]]
|
if [ "$ARG1" == "reload" ]
|
||||||
then
|
then
|
||||||
echo "Hot Reload!"
|
echo "Hot Reload!"
|
||||||
|
|
||||||
# Start API
|
# Start API
|
||||||
uvicorn mealie.app:app --host 0.0.0.0 --port 9000 --reload
|
python /app/mealie/app.py
|
||||||
else
|
else
|
||||||
echo "Production"
|
echo "Production"
|
||||||
# Web Server
|
# Web Server
|
||||||
|
@ -35,21 +35,31 @@ def minify_image(image_file: Path) -> ImageSizes:
|
|||||||
min_dest (Path): FULL Destination File Path
|
min_dest (Path): FULL Destination File Path
|
||||||
tiny_dest (Path): FULL Destination File Path
|
tiny_dest (Path): FULL Destination File Path
|
||||||
"""
|
"""
|
||||||
min_dest = image_file.parent.joinpath(f"min-original{image_file.suffix}")
|
def cleanup(dir: Path) -> None:
|
||||||
tiny_dest = image_file.parent.joinpath(f"tiny-original{image_file.suffix}")
|
for file in dir.glob("*.*"):
|
||||||
|
if file.suffix != ".webp":
|
||||||
|
file.unlink()
|
||||||
|
|
||||||
if min_dest.exists() and tiny_dest.exists():
|
org_dest = image_file.parent.joinpath(f"original.webp")
|
||||||
|
min_dest = image_file.parent.joinpath(f"min-original.webp")
|
||||||
|
tiny_dest = image_file.parent.joinpath(f"tiny-original.webp")
|
||||||
|
|
||||||
|
if min_dest.exists() and tiny_dest.exists() and org_dest.exists():
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
img = Image.open(image_file)
|
img = Image.open(image_file)
|
||||||
|
|
||||||
|
img.save(org_dest, "WEBP")
|
||||||
basewidth = 720
|
basewidth = 720
|
||||||
wpercent = basewidth / float(img.size[0])
|
wpercent = basewidth / float(img.size[0])
|
||||||
hsize = int((float(img.size[1]) * float(wpercent)))
|
hsize = int((float(img.size[1]) * float(wpercent)))
|
||||||
img = img.resize((basewidth, hsize), Image.ANTIALIAS)
|
img = img.resize((basewidth, hsize), Image.ANTIALIAS)
|
||||||
img.save(min_dest, quality=70)
|
img.save(min_dest, "WEBP", quality=70)
|
||||||
|
|
||||||
tiny_image = crop_center(img)
|
tiny_image = crop_center(img)
|
||||||
tiny_image.save(tiny_dest, quality=70)
|
tiny_image.save(tiny_dest, "WEBP", quality=70)
|
||||||
|
|
||||||
|
cleanup_images = True
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
shutil.copy(image_file, min_dest)
|
shutil.copy(image_file, min_dest)
|
||||||
@ -58,7 +68,10 @@ def minify_image(image_file: Path) -> ImageSizes:
|
|||||||
image_sizes = get_image_sizes(image_file, min_dest, tiny_dest)
|
image_sizes = get_image_sizes(image_file, min_dest, tiny_dest)
|
||||||
|
|
||||||
logger.info(f"{image_file.name} Minified: {image_sizes.org} -> {image_sizes.min} -> {image_sizes.tiny}")
|
logger.info(f"{image_file.name} Minified: {image_sizes.org} -> {image_sizes.min} -> {image_sizes.tiny}")
|
||||||
|
|
||||||
|
if cleanup_images:
|
||||||
|
cleanup(image_file.parent)
|
||||||
|
|
||||||
return image_sizes
|
return image_sizes
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user