feat(devops): add Docker scripts

This commit is contained in:
Julian Lobbes 2023-05-20 23:13:02 +02:00
parent da9ee48962
commit 9e150a4efe
19 changed files with 449 additions and 17 deletions

13
.dockerignore Normal file
View file

@ -0,0 +1,13 @@
# Frontend build output
frontend/.gitignore
frontend/.svelte-kit
frontend/build
frontend/node_modules
frontend/package
frontend/vite.config.js.timestamp-*
frontend/vite.config.ts.timestamp-*
# Backend cache files and virtualenv
backend/.venv/
*.pyc
__pycache__/

17
.gitignore vendored Normal file
View file

@ -0,0 +1,17 @@
# Local database
/.postgres/
# Latex compiled files
**/*.aux
**/*.bbl
**/*.bcf
**/*.blg
**/*.gz
**/*.log
**/*.out
**/*.run.xml
**/*.pdf
# Drawio backup and lock files
**/*.drawio.bkp
**/*.drawio.dtmp

14
Caddyfile Normal file
View file

@ -0,0 +1,14 @@
:8000 {
handle_path /api/* {
reverse_proxy * todo-backend:3000
}
handle * {
reverse_proxy * todo-frontend:3000
}
log {
output stderr
format console
}
}

View file

@ -0,0 +1,25 @@
# syntax=docker/dockerfile:1
FROM python:alpine
# Create non-root user
ARG CUSTOM_UID
ARG CUSTOM_GID
ENV CUSTOM_USERNAME=backend
ENV CUSTOM_GROUPNAME=backend
RUN addgroup --gid ${CUSTOM_GID:-1000} ${CUSTOM_GROUPNAME} && \
adduser --uid ${CUSTOM_UID:-1000} --shell /bin/ash ${CUSTOM_USERNAME} --ingroup ${CUSTOM_GROUPNAME} --disabled-password && \
mkdir /app && chown ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000} /app && chmod 700 /app
ENV PATH "$PATH:/home/${CUSTOM_GROUPNAME}/.local/bin"
# Copy source files
WORKDIR /app
COPY --chown=${CUSTOM_USERNAME}:${CUSTOM_GROUPNAME} backend/ /app/
# Install dependencies
USER ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000}
RUN pip install -r requirements.txt
# Run ASGI server
EXPOSE 3000/tcp
ENTRYPOINT ["uvicorn", "todo.main:app", "--root-path", "/api", "--host", "0.0.0.0", "--port", "3000", "--access-log", "--use-colors", "--log-level", "debug", "--reload"]

View file

@ -0,0 +1,93 @@
---
version: "3"
services:
todo-webserver:
container_name: todo-webserver
restart: unless-stopped
build:
context: .
dockerfile: development.webserver.Dockerfile
args:
CUSTOM_UID: 1000
CUSTOM_GID: 1000
environment:
TZ: Europe/Berlin
ports:
- "8000:8000"
volumes:
- ./Caddyfile:/app/Caddyfile
todo-frontend:
container_name: todo-frontend
restart: unless-stopped
depends_on:
- todo-webserver
build:
context: .
dockerfile: development.frontend.Dockerfile
args:
CUSTOM_UID: 1000
CUSTOM_GID: 1000
environment:
TZ: Europe/Berlin
expose:
- "3000"
volumes:
- ./frontend/postcss.config.js:/app/postcss.config.js:ro
- ./frontend/svelte.config.js:/app/svelte.config.js:ro
- ./frontend/tailwind.config.js:/app/tailwind.config.js:ro
- ./frontend/tsconfig.json:/app/tsconfig.json:ro
- ./frontend/vite.config.ts:/app/vite.config.ts:ro
- ./frontend/src:/app/src:ro
- ./frontend/static:/app/static:ro
todo-backend:
container_name: todo-backend
restart: unless-stopped
depends_on:
- todo-webserver
- todo-db
build:
context: .
dockerfile: ./development.backend.Dockerfile
args:
CUSTOM_UID: 1000
CUSTOM_GID: 1000
expose:
- "3000"
volumes:
- ./backend/todo/:/app/todo:ro
- ./backend/requirements.txt:/app/requirements.txt:ro
environment:
APP_NAME: "TodoApp"
ADMIN_EMAIL: "admin@example.com"
DEBUG_MODE: "true"
POSTGRES_HOST: "todo-db"
POSTGRES_PORT: "5432"
POSTGRES_DB: "todo"
POSTGRES_USER: "todo"
POSTGRES_PASSWORD: "todo"
todo-db:
image: postgres:alpine
container_name: todo-db
restart: unless-stopped
expose:
- "5432"
volumes:
- ./.postgres:/var/lib/postgresql/data
environment:
POSTGRES_DB: "todo"
POSTGRES_USER: "todo"
POSTGRES_PASSWORD: "todo"
todo-pgweb:
image: sosedoff/pgweb
container_name: todo-pgweb
restart: unless-stopped
depends_on:
- todo-db
ports:
- "8001:8081"
environment:
DATABASE_URL: "postgres://todo:todo@todo-db:5432/todo?sslmode=disable"
...

View file

@ -0,0 +1,28 @@
# syntax=docker/dockerfile:1
FROM alpine:latest
# Install npm and nodejs
RUN apk add --no-cache nodejs npm
# Create non-root user
ARG CUSTOM_UID
ARG CUSTOM_GID
ENV CUSTOM_USERNAME=frontend
ENV CUSTOM_GROUPNAME=frontend
RUN addgroup --gid ${CUSTOM_GID:-1000} ${CUSTOM_GROUPNAME} && \
adduser --uid ${CUSTOM_UID:-1000} --shell /bin/ash ${CUSTOM_USERNAME} --ingroup ${CUSTOM_GROUPNAME} --disabled-password && \
mkdir /app && chown ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000} /app && chmod 700 /app
# Copy source files
COPY --chown=${CUSTOM_USERNAME}:${CUSTOM_GROUPNAME} frontend/ /app/
# Install dependencies
USER ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000}
WORKDIR /app/
RUN npm install
# Run vite dev server
ENV NODE_ENV=development
EXPOSE 3000
ENTRYPOINT ["npm", "run", "dev"]

View file

@ -0,0 +1,24 @@
# syntax=docker/dockerfile:1
FROM alpine:latest
# Install caddy
RUN apk add --no-cache caddy
# Create non-root user
ARG CUSTOM_UID
ARG CUSTOM_GID
ENV CUSTOM_USERNAME=webserver
ENV CUSTOM_GROUPNAME=webserver
RUN addgroup --gid ${CUSTOM_GID:-1000} ${CUSTOM_GROUPNAME} && \
adduser --uid ${CUSTOM_UID:-1000} --shell /bin/ash ${CUSTOM_USERNAME} --ingroup ${CUSTOM_GROUPNAME} --disabled-password && \
mkdir /app && chown ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000} /app && chmod 700 /app
# Copy caddy config
WORKDIR /app
COPY --chown=${CUSTOM_USERNAME}:${CUSTOM_GROUPNAME} Caddyfile /app/
# Run Caddy in development mode
USER ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000}
EXPOSE 8000
ENTRYPOINT ["caddy", "run", "--config", "/app/Caddyfile", "--adapter", "caddyfile", "--watch"]

View file

@ -1,3 +1,22 @@
# Environment variable defaults for node and sveltekit
# NOTE: variables prefixed with 'PUBLIC_' are visible to clients!
PUBLIC_TITLE="Example TODO App" PUBLIC_TITLE="Example TODO App"
PUBLIC_DESCRIPTION="An example TODO app built with sveltekit and fastapi." PUBLIC_DESCRIPTION="An example TODO app built with sveltekit and fastapi."
PUBLIC_AUTHOR="John Doe" PUBLIC_AUTHOR="John Doe"
HOST="0.0.0.0"
PORT="3000"
# WARNING: only set these if running behind a trusted reverse proxy!
# See `https://kit.svelte.dev/docs/adapter-node#environment-variables-origin-protocol-header-and-host-header`.
ORIGIN=http://localhost
PROTOCOL_HEADER="x-forwarded-proto"
HOST_HEADER="x-forwarded-host"
# See `https://kit.svelte.dev/docs/adapter-node#environment-variables-address-header-and-xff-depth`.
ADDRESS_HEADER="True-Client-IP"
# Maximum request body size to accept in bytes.
# See `https://kit.svelte.dev/docs/adapter-node#environment-variables-body-size-limit`.
BODY_SIZE_LIMIT="1048576"

View file

@ -1 +1,6 @@
# See `https://kit.svelte.dev/docs/adapter-node#environment-variables-origin-protocol-header-and-host-header`.
ORIGIN=http://localhost ORIGIN=http://localhost
# See `https://kit.svelte.dev/docs/adapter-node#environment-variables-address-header-and-xff-depth`.
ADDRESS_HEADER="X-Forwarded-For"
XFF_DEPTH="1"

View file

@ -4,21 +4,16 @@ This a sveltekit project, created with the npm creation script.
## Developing ## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: Start the development Docker stack by running
```bash ```bash
npm run dev sudo docker-compose -f development.docker-compose.yml up --build --force-recreate --remove-orphans
# or start the server and open the app in a new browser tab
npm run dev -- --open
``` ```
## Building ## Building
To create a production version of your app: To run the development Docker stack, run
```bash ```bash
npm run build sudo docker-compose -f production.docker-compose.yml up --build --force-recreate --remove-orphans --detach
``` ```
You can preview the production build with `npm run preview`.

View file

@ -7,7 +7,7 @@
<meta property="og:type" content="website"> <meta property="og:type" content="website">
<meta property="og:url" content={`${$page.url}`}> <meta property="og:url" content={`${$page.url}`}>
<meta property="og:description" content={description}> <meta property="og:description" content={description}>
<meta property="og:image" content={ogImage}> <meta property="og:image" content={ogImageUrl}>
<meta property="og:image:type" content="image/webp"> <meta property="og:image:type" content="image/webp">
<meta property="og:image:width" content="1200"> <meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630"> <meta property="og:image:height" content="630">
@ -25,11 +25,11 @@
import "../app.css"; import "../app.css";
import { page } from '$app/stores'; import { page } from '$app/stores';
import ogImage from '/images/common/og-image.webp';
export let title = import.meta.env.PUBLIC_TITLE; export let title = import.meta.env.PUBLIC_TITLE;
export let description = import.meta.env.PUBLIC_DESCRIPTION; export let description = import.meta.env.PUBLIC_DESCRIPTION;
export let author = import.meta.env.PUBLIC_AUTHOR; export let author = import.meta.env.PUBLIC_AUTHOR;
const ogImageUrl = new URL('/images/common/og-image.webp', import.meta.url).href
</script> </script>
<slot /> <slot />

View file

@ -1,2 +1,6 @@
<script>
console.log(import.meta.env.MODE)
</script>
<h1>Hello World!</h1> <h1>Hello World!</h1>
<p>This is a simple Todo-App as a tech stack template for new projects.</p> <p>This is a simple Todo-App as a tech stack template for new projects.</p>

View file

@ -1,11 +1,27 @@
<script lang='ts'> <script lang='ts'>
let count = 0;
function handleClick() { function handleClick() {
count += 1; promise = getUsers();
} }
async function getUsers() {
const res = await fetch("/api/users/");
const json = await res.text();
if (res.ok) {
return json;
} else {
throw new Error(json);
}
}
let promise = getUsers();
</script> </script>
<h1>TODOs</h1> <h1>TODOs</h1>
<p>Currently, there are {count} todo-items</p>
<button on:click={handleClick}>Click Me</button> <button on:click={handleClick}>Click Me</button>
{#await promise}
<p>Waiting</p>
{:then users}
<p>{users}</p>
{:catch error}
<p style="color: red">{error}</p>
{/await}

View file

@ -6,7 +6,10 @@ const config = {
preprocess: vitePreprocess(), preprocess: vitePreprocess(),
kit: { kit: {
adapter: adapter() adapter: adapter({
// Enable gzip and brotli compression of static assets and precompiled documents if not in development
precompress: process.env.NODE_ENV !== 'development'
})
} }
}; };

View file

@ -4,4 +4,14 @@ import { defineConfig } from 'vite';
export default defineConfig({ export default defineConfig({
envPrefix: 'PUBLIC_', envPrefix: 'PUBLIC_',
plugins: [sveltekit()], plugins: [sveltekit()],
server: {
host: '0.0.0.0',
port: 3000,
strictPort: true,
},
preview: {
host: '0.0.0.0',
port: 3000,
strictPort: true,
},
}); });

View file

@ -0,0 +1,25 @@
# syntax=docker/dockerfile:1
FROM python:alpine
# Create non-root user
ARG CUSTOM_UID
ARG CUSTOM_GID
ENV CUSTOM_USERNAME=backend
ENV CUSTOM_GROUPNAME=backend
RUN addgroup --gid ${CUSTOM_GID:-1000} ${CUSTOM_GROUPNAME} && \
adduser --uid ${CUSTOM_UID:-1000} --shell /bin/ash ${CUSTOM_USERNAME} --ingroup ${CUSTOM_GROUPNAME} --disabled-password && \
mkdir /app && chown ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000} /app && chmod 700 /app
ENV PATH "$PATH:/home/${CUSTOM_GROUPNAME}/.local/bin"
# Copy source files
WORKDIR /app
COPY --chown=${CUSTOM_USERNAME}:${CUSTOM_GROUPNAME} backend/ /app/
# Install dependencies
USER ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000}
RUN pip install -r requirements.txt
# Run ASGI server
EXPOSE 3000/tcp
ENTRYPOINT ["uvicorn", "todo.main:app", "--root-path", "/api", "--host", "0.0.0.0", "--port", "3000", "--access-log"]

View file

@ -0,0 +1,88 @@
---
version: "3"
services:
todo-webserver:
container_name: todo-webserver
restart: unless-stopped
build:
context: .
dockerfile: production.webserver.Dockerfile
args:
CUSTOM_UID: 1000
CUSTOM_GID: 1000
networks:
- proxy
- todo
labels:
- "traefik.enable=true"
- "traefik.http.routers.todo.entrypoints=https"
- "traefik.http.routers.todo.rule=Host(`todo.example.com`)"
- "traefik.http.routers.todo-secure.middlewares=default@file"
- "traefik.http.routers.todo.tls=true"
- "traefik.http.services.todo.loadbalancer.server.port=8000"
- 'traefik.docker.network=proxy'
todo-frontend:
container_name: todo-frontend
restart: unless-stopped
depends_on:
- todo-webserver
build:
context: .
dockerfile: production.frontend.Dockerfile
args:
CUSTOM_UID: 1000
CUSTOM_GID: 1000
networks:
- todo
expose:
- "3000"
todo-backend:
container_name: todo-backend
restart: unless-stopped
depends_on:
- todo-webserver
- todo-db
build:
context: .
dockerfile: ./production.backend.Dockerfile
args:
CUSTOM_UID: 1000
CUSTOM_GID: 1000
networks:
- todo
expose:
- "3000"
environment:
APP_NAME: "TodoApp"
ADMIN_EMAIL: "admin@example.com"
DEBUG_MODE: "true"
POSTGRES_HOST: "todo-db"
POSTGRES_PORT: "5432"
POSTGRES_DB: "todo"
POSTGRES_USER: "todo"
POSTGRES_PASSWORD: "todo"
todo-db:
image: postgres:alpine
container_name: todo-db
restart: unless-stopped
networks:
- todo
expose:
- "5432"
volumes:
- /srv/todo/data:/var/lib/postgresql/data
environment:
TZ: Europe/Berlin
POSTGRES_DB: "todo"
POSTGRES_USER: "todo"
POSTGRES_PASSWORD: "todo"
networks:
proxy:
external: true
todo:
external: false
...

View file

@ -0,0 +1,29 @@
# syntax=docker/dockerfile:1
FROM alpine:latest
# Install npm and nodejs
RUN apk add --no-cache nodejs npm
# Create non-root user
ARG CUSTOM_UID
ARG CUSTOM_GID
ENV CUSTOM_USERNAME=frontend
ENV CUSTOM_GROUPNAME=frontend
RUN addgroup --gid ${CUSTOM_GID:-1000} ${CUSTOM_GROUPNAME} && \
adduser --uid ${CUSTOM_UID:-1000} --shell /bin/ash ${CUSTOM_USERNAME} --ingroup ${CUSTOM_GROUPNAME} --disabled-password && \
mkdir /app && chown ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000} /app && chmod 700 /app
# Copy source files
COPY --chown=${CUSTOM_USERNAME}:${CUSTOM_GROUPNAME} frontend/ /app/
# Install dependencies and build app
USER ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000}
WORKDIR /app/
RUN npm install
RUN npm run build
# Run node.js
ENV NODE_ENV=production
EXPOSE 3000
ENTRYPOINT ["node", "-r", "dotenv/config", "build"]

View file

@ -0,0 +1,24 @@
# syntax=docker/dockerfile:1
FROM alpine:latest
# Install caddy
RUN apk add --no-cache caddy
# Create non-root user
ARG CUSTOM_UID
ARG CUSTOM_GID
ENV CUSTOM_USERNAME=webserver
ENV CUSTOM_GROUPNAME=webserver
RUN addgroup --gid ${CUSTOM_GID:-1000} ${CUSTOM_GROUPNAME} && \
adduser --uid ${CUSTOM_UID:-1000} --shell /bin/ash ${CUSTOM_USERNAME} --ingroup ${CUSTOM_GROUPNAME} --disabled-password && \
mkdir /app && chown ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000} /app && chmod 700 /app
# Copy caddy config
WORKDIR /app
COPY --chown=${CUSTOM_USERNAME}:${CUSTOM_GROUPNAME} Caddyfile /app/
# Run Caddy in development mode
USER ${CUSTOM_UID:-1000}:${CUSTOM_GID:-1000}
EXPOSE 8000
ENTRYPOINT ["caddy", "run", "--config", "/app/Caddyfile", "--adapter", "caddyfile"]