feat: single container image (nginx + backend), fix fresh DB init
- deploy/Dockerfile: unified image with supervisord (nginx + uvicorn) - distribution/docker-compose.single.yml: single container option - distribution/README.md: 3 deployment options (single, compose, docker run) - Fix: add status column to chat_messages CREATE TABLE (was only in migration, broke fresh DB) - push-images.sh: builds 3 images (unified, backend, frontend)
This commit is contained in:
@@ -310,6 +310,7 @@ def init_db():
|
||||
user_id TEXT NOT NULL, role TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
model_id TEXT,
|
||||
status TEXT DEFAULT 'done',
|
||||
created_at TEXT DEFAULT (datetime('now'))
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS chat_summaries (
|
||||
|
||||
51
deploy/Dockerfile
Normal file
51
deploy/Dockerfile
Normal file
@@ -0,0 +1,51 @@
|
||||
FROM python:3.12-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# ── System deps + Chromium + Terraform + nginx + supervisor ─────────────────
|
||||
ARG TERRAFORM_VERSION=1.14.7
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
curl gcc libffi-dev unzip \
|
||||
chromium fonts-liberation fonts-dejavu-core fontconfig \
|
||||
nginx supervisor && \
|
||||
ARCH=$(dpkg --print-architecture) && \
|
||||
curl -fsSL "https://releases.hashicorp.com/terraform/${TERRAFORM_VERSION}/terraform_${TERRAFORM_VERSION}_linux_${ARCH}.zip" -o /tmp/tf.zip && \
|
||||
unzip /tmp/tf.zip -d /usr/local/bin/ && rm /tmp/tf.zip && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# ── Python deps + OCI CLI ───────────────────────────────────────────────────
|
||||
COPY backend/requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt && \
|
||||
pip install --no-cache-dir oci-cli
|
||||
|
||||
# ── Backend app ─────────────────────────────────────────────────────────────
|
||||
COPY backend/app.py .
|
||||
COPY backend/cis_reports.py .
|
||||
COPY backend/mcp_cis_server.py .
|
||||
COPY backend/gen_tf_reference.py .
|
||||
|
||||
# ── Frontend (pre-built React SPA) ─────────────────────────────────────────
|
||||
RUN rm -rf /usr/share/nginx/html/*
|
||||
COPY frontend-react/dist /usr/share/nginx/html/app
|
||||
|
||||
# ── Nginx config (proxy to localhost instead of backend hostname) ───────────
|
||||
RUN printf 'map $uri $empty {\n default "";\n}\n\nserver {\n listen 8080;\n server_name _;\n client_max_body_size 50M;\n absolute_redirect off;\n\n location / {\n root /usr/share/nginx/html/app;\n try_files $uri $uri/ /index.html;\n add_header Cache-Control "no-cache, no-store, must-revalidate" always;\n add_header X-Frame-Options "DENY" always;\n add_header X-Content-Type-Options "nosniff" always;\n add_header X-XSS-Protection "1; mode=block" always;\n add_header Referrer-Policy "strict-origin-when-cross-origin" always;\n add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;\n }\n\n location /api/ {\n proxy_pass http://127.0.0.1:8000/api/;\n proxy_set_header Host $host;\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header X-Forwarded-Proto $scheme;\n proxy_read_timeout 900s;\n proxy_send_timeout 900s;\n proxy_connect_timeout 60s;\n add_header X-Frame-Options "SAMEORIGIN" always;\n add_header X-Content-Type-Options "nosniff" always;\n }\n}\n' > /etc/nginx/sites-available/default
|
||||
|
||||
# ── Supervisord config ──────────────────────────────────────────────────────
|
||||
RUN printf '[supervisord]\nnodaemon=true\nuser=root\nlogfile=/var/log/supervisor/supervisord.log\nlogfile_maxbytes=10MB\n\n[program:backend]\ncommand=runuser -u agent -- uvicorn app:app --host 127.0.0.1 --port 8000 --workers 8\ndirectory=/app\nautostart=true\nautorestart=true\nstdout_logfile=/dev/fd/1\nstdout_logfile_maxbytes=0\nstderr_logfile=/dev/fd/2\nstderr_logfile_maxbytes=0\n\n[program:nginx]\ncommand=nginx -g "daemon off;"\nautostart=true\nautorestart=true\nstdout_logfile=/dev/fd/1\nstdout_logfile_maxbytes=0\nstderr_logfile=/dev/fd/2\nstderr_logfile_maxbytes=0\n' > /etc/supervisor/conf.d/app.conf
|
||||
|
||||
# ── Non-root user + data dir ────────────────────────────────────────────────
|
||||
RUN groupadd -r agent && useradd -r -g agent -d /app -s /bin/bash agent && \
|
||||
mkdir -p /data /var/log/supervisor && chown -R agent:agent /app /data
|
||||
|
||||
# ── Generate Terraform resource reference ───────────────────────────────────
|
||||
RUN python gen_tf_reference.py || echo "WARN: tf reference generation skipped"
|
||||
|
||||
# ── Entrypoint ──────────────────────────────────────────────────────────────
|
||||
RUN printf '#!/bin/bash\nchown -R agent:agent /data 2>/dev/null || true\nexec supervisord -c /etc/supervisor/conf.d/app.conf\n' > /entrypoint.sh && \
|
||||
chmod +x /entrypoint.sh
|
||||
|
||||
VOLUME /data
|
||||
EXPOSE 8080
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
@@ -9,12 +9,14 @@ OCIR_NAMESPACE="${OCIR_NAMESPACE:?Set OCIR_NAMESPACE}"
|
||||
OCIR_REPO_PREFIX="${OCIR_REPO_PREFIX:-oci-cis-agent}"
|
||||
IMAGE_TAG="${IMAGE_TAG:-latest}"
|
||||
|
||||
UNIFIED="${OCIR_REGION}.ocir.io/${OCIR_NAMESPACE}/${OCIR_REPO_PREFIX}:${IMAGE_TAG}"
|
||||
BACKEND="${OCIR_REGION}.ocir.io/${OCIR_NAMESPACE}/${OCIR_REPO_PREFIX}-backend:${IMAGE_TAG}"
|
||||
FRONTEND="${OCIR_REGION}.ocir.io/${OCIR_NAMESPACE}/${OCIR_REPO_PREFIX}-frontend:${IMAGE_TAG}"
|
||||
|
||||
echo "========================================"
|
||||
echo "OCI CIS Agent — Build & Push to OCIR"
|
||||
echo "========================================"
|
||||
echo "Unified: $UNIFIED"
|
||||
echo "Backend: $BACKEND"
|
||||
echo "Frontend: $FRONTEND"
|
||||
echo ""
|
||||
@@ -29,33 +31,44 @@ echo "Frontend build OK."
|
||||
# 2. Ensure buildx builder exists
|
||||
docker buildx inspect multiarch >/dev/null 2>&1 || \
|
||||
docker buildx create --name multiarch --use
|
||||
|
||||
docker buildx use multiarch
|
||||
|
||||
# 3. Login to OCIR
|
||||
echo ">>> Logging in to OCIR..."
|
||||
docker login "${OCIR_REGION}.ocir.io"
|
||||
|
||||
# 4. Build + push backend (multi-arch)
|
||||
echo ">>> Building & pushing backend (amd64 + arm64)..."
|
||||
# 4. Build + push UNIFIED image (single container — nginx + backend)
|
||||
echo ">>> Building & pushing unified image (amd64 + arm64)..."
|
||||
cd "$PROJECT_ROOT"
|
||||
docker buildx build \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
-t "$UNIFIED" \
|
||||
--push \
|
||||
-f deploy/Dockerfile .
|
||||
echo "Unified image pushed."
|
||||
|
||||
# 5. Build + push backend (multi-arch) — for docker-compose deployments
|
||||
echo ">>> Building & pushing backend (amd64 + arm64)..."
|
||||
docker buildx build \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
-t "$BACKEND" \
|
||||
--push \
|
||||
-f backend/Dockerfile backend/
|
||||
echo "Backend image pushed."
|
||||
|
||||
# 5. Build + push frontend (multi-arch)
|
||||
# 6. Build + push frontend (multi-arch) — for docker-compose deployments
|
||||
echo ">>> Building & pushing frontend (amd64 + arm64)..."
|
||||
docker buildx build \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
-t "$FRONTEND" \
|
||||
--push \
|
||||
-f deploy/Dockerfile.frontend .
|
||||
echo "Frontend image pushed."
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo "Done! Both images pushed."
|
||||
echo "Backend: $BACKEND"
|
||||
echo "Frontend: $FRONTEND"
|
||||
echo "All 3 images pushed!"
|
||||
echo "Unified: $UNIFIED (single container)"
|
||||
echo "Backend: $BACKEND (docker-compose)"
|
||||
echo "Frontend: $FRONTEND (docker-compose)"
|
||||
echo "========================================"
|
||||
|
||||
@@ -117,7 +117,7 @@ APP_SECRET=<generate with: openssl rand -hex 64>
|
||||
### 2. Login to OCIR
|
||||
|
||||
```bash
|
||||
docker login ${OCIR_REGION}.ocir.io
|
||||
docker login <OCIR_REGION>.ocir.io
|
||||
```
|
||||
|
||||
- **Username:** `<namespace>/<username>` or `<namespace>/oracleidentitycloudservice/<email>`
|
||||
@@ -125,17 +125,45 @@ docker login ${OCIR_REGION}.ocir.io
|
||||
|
||||
### 3. Run
|
||||
|
||||
**Option A — Single Container** (recommended for simplicity):
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.single.yml up -d
|
||||
```
|
||||
|
||||
One container runs everything (nginx + backend). Port `8080`.
|
||||
|
||||
**Option B — Two Containers** (recommended for production):
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Separate backend and frontend containers with internal networking.
|
||||
|
||||
**Option C — Docker Run** (no compose needed):
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name oci-cis-agent \
|
||||
-p 8080:8080 \
|
||||
-v agent-data:/data \
|
||||
-e APP_SECRET=$(openssl rand -hex 64) \
|
||||
-e TZ=America/Sao_Paulo \
|
||||
<OCIR_REGION>.ocir.io/<OCIR_NAMESPACE>/oci-cis-agent:latest
|
||||
```
|
||||
|
||||
### 4. Access
|
||||
|
||||
Open `http://localhost:8080`
|
||||
|
||||
The initial admin password is generated automatically and displayed in the backend logs:
|
||||
The initial admin password is generated automatically and displayed in the container logs:
|
||||
|
||||
```bash
|
||||
# Single container / docker run
|
||||
docker logs oci-cis-agent | grep "password"
|
||||
|
||||
# Two containers
|
||||
docker compose logs backend | grep "password"
|
||||
```
|
||||
|
||||
|
||||
31
distribution/docker-compose.single.yml
Normal file
31
distribution/docker-compose.single.yml
Normal file
@@ -0,0 +1,31 @@
|
||||
services:
|
||||
agent:
|
||||
image: ${OCIR_REGION}.ocir.io/${OCIR_NAMESPACE}/oci-cis-agent:${IMAGE_TAG:-latest}
|
||||
container_name: oci-cis-agent
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- APP_SECRET=${APP_SECRET:?Set APP_SECRET in .env (openssl rand -hex 64)}
|
||||
- JWT_EXPIRY_HOURS=${JWT_EXPIRY_HOURS:-12}
|
||||
- DATA_DIR=/data
|
||||
- CORS_ORIGINS=${CORS_ORIGINS:-}
|
||||
- TZ=${TZ:-America/Sao_Paulo}
|
||||
- OCI_CLI_SUPPRESS_FILE_PERMISSIONS_WARNING=True
|
||||
ports:
|
||||
- "${PORT:-8080}:8080"
|
||||
volumes:
|
||||
- agent-data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8080/api/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
mem_limit: 4g
|
||||
|
||||
volumes:
|
||||
agent-data:
|
||||
driver: local
|
||||
Reference in New Issue
Block a user