diff --git a/docker-compose.yaml b/docker-compose.yaml index a53421f4..6e6ba575 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -124,6 +124,7 @@ services: build: context: . dockerfile: userfront/Dockerfile + target: ${USERFRONT_BUILD_TARGET:-dev} container_name: baron_userfront env_file: - .env @@ -131,19 +132,20 @@ services: - BACKEND_URL=${BACKEND_URL:-} - USERFRONT_URL=${USERFRONT_URL} - APP_ENV=${APP_ENV} + - USERFRONT_INTERNAL_PORT=5000 + volumes: + - ./userfront/lib:/workspace/userfront/lib + - ./userfront/assets:/workspace/userfront/assets + - ./userfront/web:/workspace/userfront/web + - ./userfront/scripts:/workspace/userfront/scripts:ro + - ./scripts:/workspace/scripts:ro + - ./locales:/workspace/locales:ro networks: - baron_net - ory-net depends_on: backend: condition: service_healthy - command: > - /bin/sh -c "mkdir -p /usr/share/nginx/html/assets && - echo \"BACKEND_URL=$${BACKEND_URL}\" >> /usr/share/nginx/html/assets/.env && - echo \"USERFRONT_URL=$${USERFRONT_URL}\" >> /usr/share/nginx/html/assets/.env && - echo \"APP_ENV=$${APP_ENV}\" >> /usr/share/nginx/html/assets/.env && - cp /usr/share/nginx/html/assets/.env /usr/share/nginx/html/.env && - nginx -g 'daemon off;'" healthcheck: test: ["CMD", "wget", "-qO-", "http://127.0.0.1:5000/"] interval: 10s diff --git a/gateway/nginx.conf b/gateway/nginx.conf index 2bc97d70..f708bc63 100644 --- a/gateway/nginx.conf +++ b/gateway/nginx.conf @@ -3,6 +3,11 @@ map $time_iso8601 $time_custom { "~^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})" "$1-$2-$3 $4:$5:$6"; } +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + # Go slog 포맷과 맞춘 JSON 액세스 로그 log_format json_combined escape=json '{' @@ -100,9 +105,12 @@ server { # --- UserFront 정적 파일 프록시 --- location / { proxy_pass $userfront_upstream; + proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; } } diff --git a/test/frontend_dev_bind_mount_policy_test.sh b/test/frontend_dev_bind_mount_policy_test.sh index c28f0c4c..eb6ef3fd 100644 --- a/test/frontend_dev_bind_mount_policy_test.sh +++ b/test/frontend_dev_bind_mount_policy_test.sh @@ -3,6 +3,8 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" COMPOSE_FILE="$ROOT_DIR/docker-compose.yaml" +USERFRONT_DOCKERFILE="$ROOT_DIR/userfront/Dockerfile" +USERFRONT_DEV_SERVER="$ROOT_DIR/userfront/scripts/dev-server.sh" fail() { echo "ERROR: $*" >&2 @@ -27,6 +29,22 @@ for app in adminfront devfront orgfront; do assert_not_contains "./$app:/app" done +assert_contains 'target: ${USERFRONT_BUILD_TARGET:-dev}' +assert_contains "./userfront/lib:/workspace/userfront/lib" +assert_contains "./userfront/assets:/workspace/userfront/assets" +assert_contains "./userfront/web:/workspace/userfront/web" +assert_contains "./userfront/scripts:/workspace/userfront/scripts:ro" +assert_contains "./scripts:/workspace/scripts:ro" +assert_contains "./locales:/workspace/locales:ro" +grep -Fq -- "AS dev" "$USERFRONT_DOCKERFILE" || fail "userfront Dockerfile must define a dev build target" +grep -Fq -- "AS production" "$USERFRONT_DOCKERFILE" || fail "userfront Dockerfile must keep an explicit production target" +grep -Fq -- "flutter run" "$USERFRONT_DEV_SERVER" || fail "userfront dev server must use flutter run" +grep -Fq -- "--wasm" "$USERFRONT_DEV_SERVER" || fail "userfront dev server must keep WebAssembly enabled" +grep -Fq -- "--debug" "$USERFRONT_DEV_SERVER" || fail "userfront dev server must run in debug mode" +if grep -Fq -- "--release" "$USERFRONT_DEV_SERVER"; then + fail "userfront dev server must not run Flutter in release mode" +fi + assert_contains "./common:/workspace/common" assert_contains "/workspace/common/node_modules" assert_contains "./locales:/workspace/locales" diff --git a/test/staging_frontend_deploy_policy_test.sh b/test/staging_frontend_deploy_policy_test.sh index 8daf0daf..13bc4153 100644 --- a/test/staging_frontend_deploy_policy_test.sh +++ b/test/staging_frontend_deploy_policy_test.sh @@ -22,6 +22,7 @@ assert_not_contains() { staging_pull=".gitea/workflows/staging_code_pull.yml" pull_compose="docker/staging_pull_compose.template.yaml" deploy_compose="deploy/templates/docker-compose.yaml" +userfront_dockerfile="userfront/Dockerfile" devfront_vite="devfront/vite.config.ts" orgfront_vite="orgfront/vite.config.ts" adminfront_vite="adminfront/vite.config.ts" @@ -33,6 +34,7 @@ for file in \ "$staging_pull" \ "$pull_compose" \ "$deploy_compose" \ + "$userfront_dockerfile" \ "$adminfront_vite" \ "$devfront_vite" \ "$orgfront_vite" \ @@ -59,8 +61,15 @@ assert_contains "$staging_pull" 'chmod -R 777 config/.generated/ory' assert_contains "$staging_pull" 'docker compose -f staging_pull_compose.yaml build --pull' assert_contains "$staging_pull" 'docker compose -f staging_pull_compose.yaml up -d --remove-orphans --renew-anon-volumes' +assert_contains "$userfront_dockerfile" "FROM ghcr.io/cirruslabs/flutter:3.38.0 AS build" +assert_contains "$userfront_dockerfile" "RUN flutter build web --release --wasm" +assert_contains "$userfront_dockerfile" "FROM alpine:3.23 AS production" assert_contains "$pull_compose" "baron_devfront" assert_contains "$pull_compose" "baron_orgfront" +assert_contains "$pull_compose" "dockerfile: userfront/Dockerfile" +assert_not_contains "$pull_compose" 'target: ${USERFRONT_BUILD_TARGET:-dev}' +assert_not_contains "$pull_compose" "target: dev" +assert_not_contains "$pull_compose" "flutter run" assert_contains "$pull_compose" "http://127.0.0.1:5173/" assert_contains "$pull_compose" "http://127.0.0.1:5175/" assert_contains "$pull_compose" 'APP_ENV=${APP_ENV:-stage}' diff --git a/userfront/Dockerfile b/userfront/Dockerfile index 4bc2c939..08419496 100644 --- a/userfront/Dockerfile +++ b/userfront/Dockerfile @@ -1,3 +1,14 @@ +FROM ghcr.io/cirruslabs/flutter:3.38.0 AS dev +ENV RUN_FLUTTER_AS_ROOT=true +WORKDIR /workspace +COPY scripts ./scripts +COPY locales ./locales +COPY userfront ./userfront +WORKDIR /workspace/userfront +RUN flutter pub get +EXPOSE 5000 +CMD ["sh", "./scripts/dev-server.sh"] + # Stage 1: Build Flutter FROM ghcr.io/cirruslabs/flutter:3.38.0 AS build ENV RUN_FLUTTER_AS_ROOT=true @@ -16,7 +27,7 @@ COPY userfront/scripts/optimize-web-build.mjs /work/scripts/optimize-web-build.m RUN node /work/scripts/optimize-web-build.mjs /work/build/web # Stage 2: Serve with Nginx -FROM alpine:3.23 +FROM alpine:3.23 AS production RUN apk add --no-cache nginx nginx-mod-http-brotli # Copy built assets COPY --from=optimize /work/build/web /usr/share/nginx/html diff --git a/userfront/scripts/dev-server.sh b/userfront/scripts/dev-server.sh new file mode 100644 index 00000000..b5f63594 --- /dev/null +++ b/userfront/scripts/dev-server.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env sh +set -eu + +cd /workspace +/bin/sh ./scripts/sync_userfront_locales.sh + +cd /workspace/userfront +exec flutter run \ + -d web-server \ + --web-hostname 0.0.0.0 \ + --web-port "${USERFRONT_INTERNAL_PORT:-5000}" \ + --wasm \ + --debug \ + --no-web-resources-cdn