# ---------- STAGE 1: BUILD ----------
FROM python:3.12-slim AS builder
-WORKDIR /app
-
# Install build dependencies
RUN apt-get update && \
apt-get install -y --no-install-recommends sqlite3 && \
COPY requirements.txt .
RUN pip install --prefix=/install -r requirements.txt
-# ---------- STAGE 2: RUNTIME ----------
-FROM python:3.12-slim
+WORKDIR /app
+
+# Copy backend, frontend, entrypoint
+COPY backend backend
+COPY frontend frontend
+COPY entrypoint.py entrypoint.py
+RUN chmod 755 entrypoint.py
-WORKDIR /var/www/network-manager
+# ---------- STAGE 2: DISTROLESS ----------
+FROM gcr.io/distroless/base-debian13
-# Copy only installed packages
+# Copy Python runtime from builder
+COPY --from=builder /usr/local /usr/local
+
+# Copy libs
+COPY --from=builder /lib/x86_64-linux-gnu/libsqlite3.so.0 /lib/x86_64-linux-gnu/
+COPY --from=builder /lib/x86_64-linux-gnu/libz.so.1 /lib/x86_64-linux-gnu/
+COPY --from=builder /lib/x86_64-linux-gnu/libbz2.so.1.0 /lib/x86_64-linux-gnu/
+COPY --from=builder /lib/x86_64-linux-gnu/liblzma.so.5 /lib/x86_64-linux-gnu/
+COPY --from=builder /lib/x86_64-linux-gnu/libgcc_s.so.1 /lib/x86_64-linux-gnu/
+
+# Copy installed Python packages
COPY --from=builder /install /usr/local
-# Copy backend and frontend
-COPY backend/ /var/www/network-manager/backend/
-COPY frontend/ /var/www/network-manager/frontend/
+WORKDIR /app
+
+# Copy application
+COPY --from=builder /app/backend backend
+COPY --from=builder /app/frontend frontend
+COPY --from=builder /app/entrypoint.py /usr/local/bin/entrypoint.py
-# Copy entrypoint
-COPY entrypoint.sh /entrypoint.sh
-RUN chmod +x /entrypoint.sh
+# Ensure Python sees the installed packages
+ENV PYTHONPATH="/usr/local/lib/python3.12/site-packages"
-# Default environment variables
ENV DB_PATH=/data/database.db
ENV DB_RESET=0
ENV HTTP_PORT=8000
# Expose the port dynamically
EXPOSE ${HTTP_PORT}
-# Use the env var in the startup command
-ENTRYPOINT ["/entrypoint.sh"]
-CMD ["sh", "-c", "uvicorn backend.main:app --host 0.0.0.0 --port ${HTTP_PORT} --proxy-headers"]
+ENTRYPOINT ["/usr/local/bin/entrypoint.py"]
+CMD ["python3", "-u", "-m", "uvicorn", "backend.main:app", "--host", "0.0.0.0", "--port", "8000", "--proxy-headers"]
# FRONTEND PATHS (absolute paths inside Docker)
# ---------------------------------------------------------
-FRONTEND_DIR = "/var/www/network-manager/frontend"
+FRONTEND_DIR = "/app/frontend"
# Homepage
@app.get("/")
--- /dev/null
+#!/usr/local/bin/python3
+
+import os
+import sys
+import sqlite3
+import subprocess
+
+# ================================
+# Variables
+# ================================
+DB_FILE = os.environ.get("DB_PATH", "database.db")
+DB_RESET = os.environ.get("DB_RESET", "0") == "1"
+DOMAIN = os.environ.get("DOMAIN", "example.com")
+PUBLIC_IP = os.environ.get("PUBLIC_IP", "127.0.0.1")
+
+IMAGE_NAME = "network-manager-distroless"
+IMAGE_VERSION = "1.0"
+
+# ================================
+# Create DB if needed
+# ================================
+def create_db():
+ # Reset database if requested
+ if DB_RESET and os.path.exists(DB_FILE):
+ print("INFO: Removing existing database...")
+ os.remove(DB_FILE)
+
+ # Skip creation if DB already exists
+ if os.path.exists(DB_FILE):
+ print("INFO: Database already exists. Nothing to do.")
+ return
+
+ print(f"INFO: Creating database: {DB_FILE}.")
+
+ # Ensure directory exists
+ os.makedirs(os.path.dirname(DB_FILE) or ".", exist_ok=True)
+
+ conn = sqlite3.connect(DB_FILE)
+ cur = conn.cursor()
+
+ # Enable foreign keys
+ cur.execute("PRAGMA foreign_keys = ON;")
+
+ # GLOBAL SETTINGS
+ cur.execute("""
+ CREATE TABLE settings (
+ key TEXT PRIMARY KEY,
+ value TEXT
+ );
+ """)
+ cur.execute("INSERT INTO settings (key, value) VALUES (?, ?)", ("domain", DOMAIN))
+ cur.execute("INSERT INTO settings (key, value) VALUES (?, ?)", ("external_ipv4", PUBLIC_IP))
+
+ # HOSTS
+ cur.execute("""
+ CREATE TABLE hosts (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ name TEXT NOT NULL UNIQUE,
+ ipv4 TEXT,
+ ipv6 TEXT,
+ mac TEXT,
+ note TEXT,
+ ssl_enabled INTEGER NOT NULL DEFAULT 0
+ );
+ """)
+ cur.execute("CREATE INDEX idx_hosts_name ON hosts(name);")
+
+ # ALIASES
+ cur.execute("""
+ CREATE TABLE aliases (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ host_id INTEGER NOT NULL,
+ alias TEXT NOT NULL,
+ note TEXT,
+ ssl_enabled INTEGER NOT NULL DEFAULT 0,
+ FOREIGN KEY (host_id) REFERENCES hosts(id)
+ );
+ """)
+ cur.execute("CREATE INDEX idx_aliases_host ON aliases(host_id);")
+
+ # TXT RECORDS
+ cur.execute("""
+ CREATE TABLE txt_records (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ name TEXT NOT NULL,
+ value TEXT NOT NULL,
+ note TEXT,
+ host_id INTEGER,
+ FOREIGN KEY (host_id) REFERENCES hosts(id)
+ );
+ """)
+ cur.execute("CREATE INDEX idx_txt_host ON txt_records(host_id);")
+
+ conn.commit()
+ conn.close()
+
+ print(f"INFO: Database initialized successfully for {DOMAIN}.")
+ print(f"INFO: Public IP: {PUBLIC_IP}.")
+
+# ================================
+# Entry Point
+# ================================
+
+# Force flush
+sys.stdout.reconfigure(line_buffering=True)
+
+print(f"INFO: Starting {IMAGE_NAME} docker image version {IMAGE_VERSION}.")
+
+
+# Parse arguments
+args = sys.argv[1:]
+i = 0
+while i < len(args):
+ if args[i] == "--reset":
+ DB_RESET = True
+ i += 1
+ elif args[i] == "--domain" and i + 1 < len(args):
+ DOMAIN = args[i + 1]
+ i += 2
+ elif args[i] == "--public-ip" and i + 1 < len(args):
+ PUBLIC_IP = args[i + 1]
+ i += 2
+ elif args[i] == "--":
+ args = args[i + 1:]
+ break
+ else:
+ break
+
+# Create DB
+create_db()
+
+# Continue to CMD
+if not args:
+ print("ERROR: No command provided to exec.")
+ sys.exit(1)
+
+os.execvp(args[0], args)
+++ /dev/null
-#!/bin/bash
-set -euo pipefail
-
-# ================================
-# Variables
-# ================================
-DB_FILE="${DB_PATH:-database.db}"
-DB_RESET="${DB_RESET:-0}"
-DOMAIN="${DOMAIN:-example.com}"
-PUBLIC_IP="${PUBLIC_IP:-127.0.0.1}"
-
-IMAGE_NAME="network-manager"
-IMAGE_VERSION="0.0"
-
-function create_db() {
- # Reset database if requested
- if [[ $DB_RESET -eq 1 && -f "$DB_FILE" ]]; then
- echo "INFO: [✓]] Removing existing database."
- rm -f "$DB_FILE"
- fi
-
- # Skip creation if DB already exists
- if [[ -f "$DB_FILE" ]]; then
- echo "INFO: [✓] Database already exists. Nothing to do."
- return 0
- fi
-
- echo "INFO: [✓] Creating database: $DB_FILE"
-
- # Create DB with dynamic settings
- sqlite3 "$DB_FILE" <<EOF
-PRAGMA foreign_keys = ON;
-
--- ============================================
--- GLOBAL SETTINGS
--- ============================================
-CREATE TABLE settings (
- key TEXT PRIMARY KEY,
- value TEXT
-);
-
-INSERT INTO settings (key, value) VALUES ('domain', '${DOMAIN}');
-INSERT INTO settings (key, value) VALUES ('external_ipv4', '${PUBLIC_IP}');
-
--- ============================================
--- HOSTS
--- ============================================
-CREATE TABLE hosts (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- name TEXT NOT NULL UNIQUE,
- ipv4 TEXT,
- ipv6 TEXT,
- mac TEXT,
- note TEXT,
- ssl_enabled INTEGER NOT NULL DEFAULT 0
-);
-
-CREATE INDEX idx_hosts_name ON hosts(name);
-
--- ============================================
--- ALIASES
--- ============================================
-CREATE TABLE aliases (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- host_id INTEGER NOT NULL,
- alias TEXT NOT NULL,
- note TEXT,
- ssl_enabled INTEGER NOT NULL DEFAULT 0,
- FOREIGN KEY (host_id) REFERENCES hosts(id)
-);
-
-CREATE INDEX idx_aliases_host ON aliases(host_id);
-
--- ============================================
--- TXT RECORDS
--- ============================================
-CREATE TABLE txt_records (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- name TEXT NOT NULL,
- value TEXT NOT NULL,
- note TEXT,
- host_id INTEGER,
- FOREIGN KEY (host_id) REFERENCES hosts(id)
-);
-
-CREATE INDEX idx_txt_host ON txt_records(host_id);
-EOF
-
- echo "INFO: [✓] Database initialized successfully for $DOMAIN."
- echo "INFO: [✓] Public IP: $PUBLIC_IP."
-}
-
-# ================================
-# Entry Point
-# ================================
-
-echo "INFO: Starting $IMAGE_NAME docker image version $IMAGE_VERSION."
-
-# Parse arguments
-while [[ $# -gt 0 ]]; do
- case "$1" in
- --reset)
- DB_RESET=1
- shift
- ;;
- --domain)
- DOMAIN="$2"
- shift 2
- ;;
- --public-ip)
- PUBLIC_IP="$2"
- shift 2
- ;;
- --)
- shift
- break
- ;;
- *)
- break
- ;;
- esac
-done
-
-create_db
-
-# ================================
-# Continue to CMD
-# ================================
-exec "$@"