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:
Hayden 2021-04-29 17:47:01 -08:00 committed by GitHub
parent 7153ff6f25
commit 3e80947a4c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 110 additions and 78 deletions

View File

@ -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
} }

View File

@ -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

View File

@ -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"]

View File

@ -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:

View File

@ -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

View File

@ -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`;
}, },
}; };

View File

@ -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() {

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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