Compare commits

...
Author SHA1 Message Date
GSC-CODEXandmultica-agent cf082d04e4 BAI-29: add Docker deployment
Co-authored-by: multica-agent <github@multica.ai>
2026-08-07 09:43:34 +08:00
9 changed files with 187 additions and 2 deletions
+13
View File
@@ -0,0 +1,13 @@
.git
.gitignore
.impeccable
.venv
__pycache__
*.py[cod]
*.xls
*.xlsx
screenshots
tests
docs
design-system
流水模板
+1
View File
@@ -0,0 +1 @@
APP_PORT=4173
+27
View File
@@ -0,0 +1,27 @@
FROM python:3.12-slim
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PYTHONPATH=/app/src \
APP_HOST=0.0.0.0 \
APP_PORT=4173
WORKDIR /app
RUN addgroup --system app && adduser --system --ingroup app app
COPY requirements.txt ./
RUN python -m pip install --no-cache-dir --requirement requirements.txt
COPY server.py ./
COPY src ./src
COPY web ./web
USER app
EXPOSE 4173
HEALTHCHECK --interval=10s --timeout=3s --start-period=5s --retries=5 \
CMD ["python", "-c", "import os, urllib.request; port = os.getenv('APP_PORT', '4173'); urllib.request.urlopen(f'http://127.0.0.1:{port}/', timeout=2)"]
CMD ["python", "server.py"]
+18
View File
@@ -24,6 +24,24 @@ python -m unittest discover -s tests -v
python server.py
```
## Docker 部署
Windows PowerShell 一键启动:
```powershell
.\start-docker.ps1
```
Linux 或 macOS 一键启动:
```sh
./start-docker.sh
```
默认访问 `http://localhost:4173/`。如需更换宿主机端口,可运行
`.\start-docker.ps1 -Port 8080`,或运行 `APP_PORT=8080 ./start-docker.sh`
停止服务使用 `docker compose down`,查看日志使用 `docker compose logs -f app`
访问地址:
- 登录入口:`http://127.0.0.1:4173/`
+17
View File
@@ -0,0 +1,17 @@
services:
app:
build:
context: .
image: caiwuzongzhang:local
ports:
- "${APP_PORT:-4173}:4173"
environment:
APP_HOST: 0.0.0.0
APP_PORT: 4173
init: true
restart: unless-stopped
read_only: true
tmpfs:
- /tmp:size=64m,mode=1777
security_opt:
- no-new-privileges:true
+3 -2
View File
@@ -106,9 +106,10 @@ class AppHandler(SimpleHTTPRequestHandler):
def main() -> None:
host = os.environ.get("APP_HOST", "127.0.0.1")
port = int(os.environ.get("APP_PORT", "4173"))
server = ThreadingHTTPServer(("127.0.0.1", port), AppHandler)
print(f"Serving on http://127.0.0.1:{port}")
server = ThreadingHTTPServer((host, port), AppHandler)
print(f"Serving on http://{host}:{port}")
server.serve_forever()
+43
View File
@@ -0,0 +1,43 @@
param(
[ValidateRange(1, 65535)]
[int]$Port = 4173
)
$ErrorActionPreference = "Stop"
docker compose version | Out-Null
if ($LASTEXITCODE -ne 0) {
throw "Docker Compose is not available."
}
docker info | Out-Null
if ($LASTEXITCODE -ne 0) {
throw "Docker is not running."
}
$env:APP_PORT = $Port.ToString()
docker compose up --build --detach
if ($LASTEXITCODE -ne 0) {
throw "Docker Compose failed to start the application."
}
$containerId = (docker compose ps --quiet app).Trim()
if (-not $containerId) {
throw "The application container was not created."
}
for ($attempt = 0; $attempt -lt 60; $attempt++) {
$state = docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' $containerId
if ($state -eq "healthy") {
Write-Host "Application is ready: http://localhost:$Port"
exit 0
}
if ($state -eq "unhealthy" -or $state -eq "exited" -or $state -eq "dead") {
docker compose logs --no-color --tail 100 app
throw "The application container entered state: $state"
}
Start-Sleep -Seconds 1
}
docker compose logs --no-color --tail 100 app
throw "Timed out waiting for the application health check."
+48
View File
@@ -0,0 +1,48 @@
#!/usr/bin/env sh
set -eu
APP_PORT="${APP_PORT:-${1:-4173}}"
export APP_PORT
case "$APP_PORT" in
''|*[!0-9]*)
echo "APP_PORT must be an integer between 1 and 65535." >&2
exit 1
;;
esac
if [ "$APP_PORT" -lt 1 ] || [ "$APP_PORT" -gt 65535 ]; then
echo "APP_PORT must be an integer between 1 and 65535." >&2
exit 1
fi
docker compose version >/dev/null
docker info >/dev/null
docker compose up --build --detach
container_id="$(docker compose ps --quiet app)"
if [ -z "$container_id" ]; then
echo "The application container was not created." >&2
exit 1
fi
attempt=0
while [ "$attempt" -lt 60 ]; do
state="$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "$container_id")"
case "$state" in
healthy)
echo "Application is ready: http://localhost:${APP_PORT}"
exit 0
;;
unhealthy|exited|dead)
docker compose logs --no-color --tail 100 app
echo "The application container entered state: $state" >&2
exit 1
;;
esac
attempt=$((attempt + 1))
sleep 1
done
docker compose logs --no-color --tail 100 app
echo "Timed out waiting for the application health check." >&2
exit 1
+17
View File
@@ -0,0 +1,17 @@
from __future__ import annotations
import os
from unittest import TestCase
from unittest.mock import patch
import server
class ServerConfigurationTests(TestCase):
@patch.dict(os.environ, {"APP_HOST": "0.0.0.0", "APP_PORT": "8080"})
@patch("server.ThreadingHTTPServer")
def test_main_uses_configured_host_and_port(self, http_server) -> None:
server.main()
http_server.assert_called_once_with(("0.0.0.0", 8080), server.AppHandler)
http_server.return_value.serve_forever.assert_called_once_with()