feat(docker): add split backend/frontend stacks and dynamic versioning

This commit is contained in:
Alphaeus Mote
2025-11-19 09:13:21 -05:00
parent be6d358086
commit f294047c87
19 changed files with 696 additions and 8 deletions
+34
View File
@@ -0,0 +1,34 @@
###BEGIN GENERAL###
STACK_NAME=stk-depl0y-backend-001
STACK_BINDMOUNTROOT=custom/docker/stacks
TZ=America/New_York
UID=0
GID=0
SERVICE_BIND_ADDRESS_EXTERNAL=0.0.0.0
SERVICE_BIND_ADDRESS_INTERNAL=127.0.0.1
DNSSERVER=1.1.1.1
###END GENERAL###
###BEGIN BACKEND###
BACKEND_IMAGENAME=Agit8or/Depl0y-backend
BACKEND_IMAGEVERSION=latest
BACKEND_ENABLEAUTOMATICUPDATES=true
BACKEND_PORT_EXTERNAL=8081
###END BACKEND###
###BEGIN APPLICATION###
SECRET_KEY=changeme
ENCRYPTION_KEY=changeme
LOG_LEVEL=INFO
ADMIN_USERNAME=admin
ADMIN_PASSWORD=admin
# Optional path overrides inside the container
DATABASE_URL=sqlite:////var/lib/depl0y/db/depl0y.db
ISO_STORAGE_PATH=/var/lib/depl0y/isos
UPLOAD_DIR=/var/lib/depl0y
CLOUDINIT_TEMPLATE_PATH=/var/lib/depl0y/cloud-init
SSH_KEY_PATH=/var/lib/depl0y/ssh_keys
LOG_FILE=/var/log/depl0y/app.log
###END APPLICATION###
+61
View File
@@ -0,0 +1,61 @@
FROM python:3.11-slim
ARG APP_VERSION=1.1.9
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
APP_VERSION=${APP_VERSION}
# System dependencies required by the backend (SSH, PDF, etc.)
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential \
curl \
wget \
sshpass \
openssh-client \
ca-certificates \
libpango-1.0-0 \
libpangocairo-1.0-0 \
libgdk-pixbuf2.0-0 \
libffi-dev \
shared-mime-info \
&& rm -rf /var/lib/apt/lists/*
# Create application directories
RUN mkdir -p /opt/depl0y/backend \
/var/lib/depl0y/db \
/var/lib/depl0y/cloud-images \
/var/lib/depl0y/isos \
/var/lib/depl0y/ssh_keys \
/var/lib/depl0y/cloud-init \
/var/log/depl0y
WORKDIR /opt/depl0y/backend
# Install Python dependencies
COPY src/backend/requirements.txt /opt/depl0y/backend/requirements.txt
RUN pip install --no-cache-dir --upgrade pip \
&& pip install --no-cache-dir -r requirements.txt \
&& pip install --no-cache-dir weasyprint markdown requests
# Copy backend code
COPY src/backend /opt/depl0y/backend
# Default environment values (can be overridden at runtime)
ENV DATABASE_URL=sqlite:////var/lib/depl0y/db/depl0y.db \
ISO_STORAGE_PATH=/var/lib/depl0y/isos \
UPLOAD_DIR=/var/lib/depl0y \
CLOUDINIT_TEMPLATE_PATH=/var/lib/depl0y/cloud-init \
SSH_KEY_PATH=/var/lib/depl0y/ssh_keys \
LOG_FILE=/var/log/depl0y/app.log \
APPLICATION_PORT_INTERNAL=8080
# Entrypoint script
COPY deployment/docker/backend/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
EXPOSE 8080
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
IMAGE_NAME="${IMAGE_NAME:-Agit8or/Depl0y-backend}"
APP_VERSION="${APP_VERSION:-$(date +%Y.%m.%d.%H%M)}"
VERSION_TAG="${IMAGE_TAG:-${APP_VERSION}}"
echo "Building ${IMAGE_NAME}:${VERSION_TAG} and ${IMAGE_NAME}:latest from ${ROOT_DIR}..."
docker build \
-f "${ROOT_DIR}/deployment/docker/backend/Dockerfile" \
--build-arg APP_VERSION="${VERSION_TAG}" \
-t "${IMAGE_NAME}:${VERSION_TAG}" \
-t "${IMAGE_NAME}:latest" \
"${ROOT_DIR}"
if [ "${PUSH:-false}" = "true" ]; then
echo "Pushing ${IMAGE_NAME}:${VERSION_TAG} and ${IMAGE_NAME}:latest..."
docker push "${IMAGE_NAME}:${VERSION_TAG}"
docker push "${IMAGE_NAME}:latest"
fi
echo "Done."
@@ -0,0 +1,59 @@
name: '${STACK_NAME:-stk-depl0y-backend-001}'
networks:
EXTERNAL:
name: DEPL0Y-BACKEND-EXTERNAL
driver: bridge
internal: false
attachable: true
INTERNAL:
name: DEPL0Y-BACKEND-INTERNAL
driver: bridge
internal: true
attachable: true
services:
Backend:
image: '${BACKEND_IMAGENAME:-Agit8or/Depl0y-backend}:${BACKEND_IMAGEVERSION:-latest}'
container_name: DEPL0Y-BACKEND-001
hostname: DEPL0Y-BACKEND-001
restart: unless-stopped
stop_signal: SIGTERM
stop_grace_period: 90s
user: '${UID:-0}:${GID:-0}'
logging:
driver: 'local'
#env_file: '.env'
environment:
SECRET_KEY: '${SECRET_KEY:-changeme}'
ENCRYPTION_KEY: '${ENCRYPTION_KEY:-changeme}'
LOG_LEVEL: '${LOG_LEVEL:-INFO}'
DATABASE_URL: '${DATABASE_URL:-sqlite:////var/lib/depl0y/db/depl0y.db}'
ISO_STORAGE_PATH: '${ISO_STORAGE_PATH:-/var/lib/depl0y/isos}'
UPLOAD_DIR: '${UPLOAD_DIR:-/var/lib/depl0y}'
CLOUDINIT_TEMPLATE_PATH: '${CLOUDINIT_TEMPLATE_PATH:-/var/lib/depl0y/cloud-init}'
SSH_KEY_PATH: '${SSH_KEY_PATH:-/var/lib/depl0y/ssh_keys}'
LOG_FILE: '${LOG_FILE:-/var/log/depl0y/app.log}'
APPLICATION_PORT_INTERNAL: '${APPLICATION_PORT_INTERNAL:-8080}'
ADMIN_USERNAME: '${ADMIN_USERNAME:-admin}'
ADMIN_PASSWORD: '${ADMIN_PASSWORD:-admin}'
networks:
INTERNAL:
EXTERNAL:
ports:
- "${SERVICE_BIND_ADDRESS_EXTERNAL:-0.0.0.0}:${BACKEND_PORT_EXTERNAL:-8081}:${APPLICATION_PORT_INTERNAL:-8080}"
dns:
- '${DNSSERVER:-127.0.0.53}'
volumes:
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
- "/${STACK_BINDMOUNTROOT:-custom/docker/stacks}/${STACK_NAME:-stk-depl0y-backend-001}/Backend/Data:/var/lib/depl0y:rw"
- "/${STACK_BINDMOUNTROOT:-custom/docker/stacks}/${STACK_NAME:-stk-depl0y-backend-001}/Backend/Logs:/var/log/depl0y:rw"
- "/${STACK_BINDMOUNTROOT:-custom/docker/stacks}/${STACK_NAME:-stk-depl0y-backend-001}/Backend/CloudInit:/var/lib/depl0y/cloud-init:rw"
- "/${STACK_BINDMOUNTROOT:-custom/docker/stacks}/${STACK_NAME:-stk-depl0y-backend-001}/Backend/SSHKeys:/var/lib/depl0y/ssh_keys:rw"
- "/${STACK_BINDMOUNTROOT:-custom/docker/stacks}/${STACK_NAME:-stk-depl0y-backend-001}/Backend/ISOs:/var/lib/depl0y/isos:rw"
- "/${STACK_BINDMOUNTROOT:-custom/docker/stacks}/${STACK_NAME:-stk-depl0y-backend-001}/Backend/CloudImages:/var/lib/depl0y/cloud-images:rw"
labels:
com.centurylinklabs.watchtower.enable: ${BACKEND_ENABLEAUTOMATICUPDATES:-true}
@@ -0,0 +1,32 @@
#!/usr/bin/env sh
set -e
APP_PORT="${APPLICATION_PORT_INTERNAL:-8080}"
# Ensure core data directories exist (may be bind-mounted)
mkdir -p /var/lib/depl0y/db \
/var/lib/depl0y/cloud-images \
/var/lib/depl0y/isos \
/var/lib/depl0y/ssh_keys \
/var/lib/depl0y/cloud-init \
/var/log/depl0y
echo "Initializing database schema..."
python - <<'PY'
from app.core.database import init_db
init_db()
PY
echo "Database schema initialized."
echo "Initializing system settings..."
python /opt/depl0y/backend/init_system_settings.py || echo "Warning: system settings initialization failed"
echo "System settings initialization step completed."
echo "Ensuring default admin user exists..."
python /opt/depl0y/backend/init_admin_user.py || echo "Warning: admin user initialization failed"
echo "Admin user initialization step completed."
echo "Starting Depl0y backend with uvicorn on port ${APP_PORT}..."
exec uvicorn app.main:app --host 0.0.0.0 --port "${APP_PORT}"
+37
View File
@@ -0,0 +1,37 @@
# Stack identification
STACK_NAME=stk-depl0y-001
STACK_BINDMOUNTROOT=custom/docker/stacks
# Image configuration
APPLICATION_IMAGENAME=Agit8or/Depl0y
APPLICATION_IMAGEVERSION=latest
# Networking
SERVICE_BIND_ADDRESS_EXTERNAL=0.0.0.0
APPLICATION_PORT_EXTERNAL=8080
APPLICATION_PORT_INTERNAL=8080
DNSSERVER=127.0.0.53
# Runtime user (mapped to host UID/GID)
UID=1000
GID=1000
# Application secrets (change these!)
SECRET_KEY=changeme
ENCRYPTION_KEY=changeme
LOG_LEVEL=INFO
ADMIN_USERNAME=admin
ADMIN_PASSWORD=admin
# Paths inside the container (normally leave at defaults)
DATABASE_URL=sqlite:////var/lib/depl0y/db/depl0y.db
ISO_STORAGE_PATH=/var/lib/depl0y/isos
UPLOAD_DIR=/var/lib/depl0y
CLOUDINIT_TEMPLATE_PATH=/var/lib/depl0y/cloud-init
SSH_KEY_PATH=/var/lib/depl0y/ssh_keys
LOG_FILE=/var/log/depl0y/app.log
FRONTEND_DIST_PATH=/opt/depl0y/frontend/dist
# Watchtower / auto-update
APPLICATION_ENABLEAUTOMATICUPDATES=true
+64
View File
@@ -0,0 +1,64 @@
FROM python:3.11-slim
ARG APP_VERSION=1.1.9
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
APP_VERSION=${APP_VERSION}
# System dependencies required by the backend (SSH, PDF, etc.)
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential \
curl \
wget \
sshpass \
openssh-client \
ca-certificates \
libpango-1.0-0 \
libpangocairo-1.0-0 \
libgdk-pixbuf2.0-0 \
libffi-dev \
shared-mime-info \
&& rm -rf /var/lib/apt/lists/*
# Create application directories
RUN mkdir -p /opt/depl0y/backend \
/opt/depl0y/frontend/dist \
/var/lib/depl0y/db \
/var/lib/depl0y/cloud-images \
/var/lib/depl0y/isos \
/var/lib/depl0y/ssh_keys \
/var/lib/depl0y/cloud-init \
/var/log/depl0y
WORKDIR /opt/depl0y/backend
# Install Python dependencies
COPY src/backend/requirements.txt /opt/depl0y/backend/requirements.txt
RUN pip install --no-cache-dir --upgrade pip \
&& pip install --no-cache-dir -r requirements.txt \
&& pip install --no-cache-dir weasyprint markdown requests
# Copy backend code and built frontend assets
COPY src/backend /opt/depl0y/backend
COPY src/frontend/dist /opt/depl0y/frontend/dist
# Default environment values (can be overridden at runtime)
ENV DATABASE_URL=sqlite:////var/lib/depl0y/db/depl0y.db \
ISO_STORAGE_PATH=/var/lib/depl0y/isos \
UPLOAD_DIR=/var/lib/depl0y \
CLOUDINIT_TEMPLATE_PATH=/var/lib/depl0y/cloud-init \
SSH_KEY_PATH=/var/lib/depl0y/ssh_keys \
LOG_FILE=/var/log/depl0y/app.log \
FRONTEND_DIST_PATH=/opt/depl0y/frontend/dist \
APPLICATION_PORT_INTERNAL=8080
# Entrypoint script
COPY deployment/docker/combined/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
EXPOSE 8080
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
IMAGE_NAME="${IMAGE_NAME:-Agit8or/Depl0y}"
APP_VERSION="${APP_VERSION:-$(date +%Y.%m.%d.%H%M)}"
VERSION_TAG="${IMAGE_TAG:-${APP_VERSION}}"
echo "Building ${IMAGE_NAME}:${VERSION_TAG} and ${IMAGE_NAME}:latest from ${ROOT_DIR}..."
docker build \
-f "${ROOT_DIR}/deployment/docker/combined/Dockerfile" \
--build-arg APP_VERSION="${VERSION_TAG}" \
-t "${IMAGE_NAME}:${VERSION_TAG}" \
-t "${IMAGE_NAME}:latest" \
"${ROOT_DIR}"
if [ "${PUSH:-false}" = "true" ]; then
echo "Pushing ${IMAGE_NAME}:${VERSION_TAG} and ${IMAGE_NAME}:latest..."
docker push "${IMAGE_NAME}:${VERSION_TAG}"
docker push "${IMAGE_NAME}:latest"
fi
echo "Done."
@@ -0,0 +1,60 @@
name: '${STACK_NAME:-stk-depl0y-001}'
networks:
EXTERNAL:
name: DEPL0Y-EXTERNAL
driver: bridge
internal: false
attachable: true
INTERNAL:
name: DEPL0Y-INTERNAL
driver: bridge
internal: true
attachable: true
services:
Application:
image: '${APPLICATION_IMAGENAME:-Agit8or/Depl0y}:${APPLICATION_IMAGEVERSION:-latest}'
container_name: DEPL0Y-APP-001
hostname: DEPL0Y-APP-001
restart: unless-stopped
stop_signal: SIGTERM
stop_grace_period: 90s
user: '${UID:-0}:${GID:-0}'
logging:
driver: 'local'
#env_file: '.env'
environment:
SECRET_KEY: '${SECRET_KEY:-changeme}'
ENCRYPTION_KEY: '${ENCRYPTION_KEY:-changeme}'
LOG_LEVEL: '${LOG_LEVEL:-INFO}'
DATABASE_URL: '${DATABASE_URL:-sqlite:////var/lib/depl0y/db/depl0y.db}'
ISO_STORAGE_PATH: '${ISO_STORAGE_PATH:-/var/lib/depl0y/isos}'
UPLOAD_DIR: '${UPLOAD_DIR:-/var/lib/depl0y}'
CLOUDINIT_TEMPLATE_PATH: '${CLOUDINIT_TEMPLATE_PATH:-/var/lib/depl0y/cloud-init}'
SSH_KEY_PATH: '${SSH_KEY_PATH:-/var/lib/depl0y/ssh_keys}'
LOG_FILE: '${LOG_FILE:-/var/log/depl0y/app.log}'
FRONTEND_DIST_PATH: '${FRONTEND_DIST_PATH:-/opt/depl0y/frontend/dist}'
APPLICATION_PORT_INTERNAL: '${APPLICATION_PORT_INTERNAL:-8080}'
ADMIN_USERNAME: '${ADMIN_USERNAME:-admin}'
ADMIN_PASSWORD: '${ADMIN_PASSWORD:-admin}'
networks:
INTERNAL:
EXTERNAL:
ports:
- "${SERVICE_BIND_ADDRESS_EXTERNAL:-0.0.0.0}:${APPLICATION_PORT_EXTERNAL:-8080}:${APPLICATION_PORT_INTERNAL:-8080}"
dns:
- '${DNSSERVER:-127.0.0.53}'
volumes:
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
- "/${STACK_BINDMOUNTROOT:-custom/docker/stacks}/${STACK_NAME:-stk-depl0y-001}/Application/Data:/var/lib/depl0y:rw"
- "/${STACK_BINDMOUNTROOT:-custom/docker/stacks}/${STACK_NAME:-stk-depl0y-001}/Application/Logs:/var/log/depl0y:rw"
- "/${STACK_BINDMOUNTROOT:-custom/docker/stacks}/${STACK_NAME:-stk-depl0y-001}/Application/CloudInit:/var/lib/depl0y/cloud-init:rw"
- "/${STACK_BINDMOUNTROOT:-custom/docker/stacks}/${STACK_NAME:-stk-depl0y-001}/Application/SSHKeys:/var/lib/depl0y/ssh_keys:rw"
- "/${STACK_BINDMOUNTROOT:-custom/docker/stacks}/${STACK_NAME:-stk-depl0y-001}/Application/ISOs:/var/lib/depl0y/isos:rw"
- "/${STACK_BINDMOUNTROOT:-custom/docker/stacks}/${STACK_NAME:-stk-depl0y-001}/Application/CloudImages:/var/lib/depl0y/cloud-images:rw"
labels:
com.centurylinklabs.watchtower.enable: ${APPLICATION_ENABLEAUTOMATICUPDATES:-true}
@@ -0,0 +1,32 @@
#!/usr/bin/env sh
set -e
APP_PORT="${APPLICATION_PORT_INTERNAL:-8080}"
# Ensure core data directories exist (may be bind-mounted)
mkdir -p /var/lib/depl0y/db \
/var/lib/depl0y/cloud-images \
/var/lib/depl0y/isos \
/var/lib/depl0y/ssh_keys \
/var/lib/depl0y/cloud-init \
/var/log/depl0y
echo "Initializing database schema..."
python - <<'PY'
from app.core.database import init_db
init_db()
PY
echo "Database schema initialized."
echo "Initializing system settings..."
python /opt/depl0y/backend/init_system_settings.py || echo "Warning: system settings initialization failed"
echo "System settings initialization step completed."
echo "Ensuring default admin user exists..."
python /opt/depl0y/backend/init_admin_user.py || echo "Warning: admin user initialization failed"
echo "Admin user initialization step completed."
echo "Starting Depl0y with uvicorn on port ${APP_PORT}..."
exec uvicorn app.main:app --host 0.0.0.0 --port "${APP_PORT}"
+18
View File
@@ -0,0 +1,18 @@
###BEGIN GENERAL###
STACK_NAME=stk-depl0y-frontend-001
STACK_BINDMOUNTROOT=custom/docker/stacks
TZ=America/New_York
UID=0
GID=0
SERVICE_BIND_ADDRESS_EXTERNAL=0.0.0.0
SERVICE_BIND_ADDRESS_INTERNAL=127.0.0.1
DNSSERVER=1.1.1.1
###END GENERAL###
###BEGIN FRONTEND###
FRONTEND_IMAGENAME=Agit8or/Depl0y-frontend
FRONTEND_IMAGEVERSION=latest
FRONTEND_ENABLEAUTOMATICUPDATES=true
FRONTEND_PORT_EXTERNAL=8080
###END FRONTEND###
+16
View File
@@ -0,0 +1,16 @@
FROM nginx:alpine
ARG APP_VERSION=1.1.9
ENV APP_VERSION=${APP_VERSION}
# Copy built frontend assets into nginx web root
COPY src/frontend/dist /usr/share/nginx/html
# Custom nginx config for SPA routing (serve index.html for unknown routes)
COPY deployment/docker/frontend/nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
IMAGE_NAME="${IMAGE_NAME:-Agit8or/Depl0y-frontend}"
APP_VERSION="${APP_VERSION:-$(date +%Y.%m.%d.%H%M)}"
VERSION_TAG="${IMAGE_TAG:-${APP_VERSION}}"
echo "Building ${IMAGE_NAME}:${VERSION_TAG} and ${IMAGE_NAME}:latest from ${ROOT_DIR}..."
docker build \
-f "${ROOT_DIR}/deployment/docker/frontend/Dockerfile" \
--build-arg APP_VERSION="${VERSION_TAG}" \
-t "${IMAGE_NAME}:${VERSION_TAG}" \
-t "${IMAGE_NAME}:latest" \
"${ROOT_DIR}"
if [ "${PUSH:-false}" = "true" ]; then
echo "Pushing ${IMAGE_NAME}:${VERSION_TAG} and ${IMAGE_NAME}:latest..."
docker push "${IMAGE_NAME}:${VERSION_TAG}"
docker.push "${IMAGE_NAME}:latest"
fi
echo "Done."
@@ -0,0 +1,33 @@
name: '${STACK_NAME:-stk-depl0y-frontend-001}'
networks:
EXTERNAL:
name: DEPL0Y-FRONTEND-EXTERNAL
driver: bridge
internal: false
attachable: true
services:
Frontend:
image: '${FRONTEND_IMAGENAME:-Agit8or/Depl0y-frontend}:${FRONTEND_IMAGEVERSION:-latest}'
container_name: DEPL0Y-FRONTEND-001
hostname: DEPL0Y-FRONTEND-001
restart: unless-stopped
stop_signal: SIGTERM
stop_grace_period: 90s
user: '${UID:-0}:${GID:-0}'
logging:
driver: 'local'
#env_file: '.env'
networks:
EXTERNAL:
ports:
- "${SERVICE_BIND_ADDRESS_EXTERNAL:-0.0.0.0}:${FRONTEND_PORT_EXTERNAL:-8080}:80"
dns:
- '${DNSSERVER:-127.0.0.53}'
volumes:
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
labels:
com.centurylinklabs.watchtower.enable: ${FRONTEND_ENABLEAUTOMATICUPDATES:-true}
+12
View File
@@ -0,0 +1,12 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri /index.html;
}
}