You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Docker Swarm部署时Flask出现TemplateNotFound错误求助

问题:Docker Swarm部署后Flask模板加载失败(TemplateNotFound)

背景

原有服务通过Docker Compose启动正常,为使用Docker Swarm的secrets保护敏感信息切换部署方式后,UI服务的Flask模板无法加载,抛出jinja2.exceptions.TemplateNotFound: public/index.html错误,但非模板渲染的调试页面、Consul注册等功能正常。

相关配置与信息

精简后的Swarm版docker-compose.yml

version: '3.7'
services:
    postgresql:
        image: docker.io/bitnami/postgresql:12.11.0
        volumes:
            - 'postgresql_data:/bitnami/postgresql'
        environment:
            #- ALLOW_EMPTY_PASSWORD=yes
            - POSTGRESQL_USERNAME=user
            - POSTGRESQL_DATABASE=user
            - POSTGRESQL_PASSWORD_FILE=/path/to/docker/secret
        secrets:
            - secret

    keycloak:
        image: docker.io/bitnami/keycloak:16.1.1
        depends_on:
            - postgresql
        environment:
            - KEYCLOAK_ADMIN_USER=user
            - KEYCLOAK_ADMIN_PASSWORD_FILE=/path/to/docker/secret
            - KEYCLOAK_DATABASE_PORT=5432
            - KEYCLOAK_DATABASE_HOST=postgresql
            - KEYCLOAK_DATABASE_NAME=user
            - KEYCLOAK_DATABASE_USER=user
            - KEYCLOAK_DATABASE_PASSWORD_FILE=/path/to/docker/secret
            - KEYCLOAK_HTTP_PORT=8083
            - KEYCLOAK_HTTPS_PORT=8443
            - KEYCLOAK_ENABLE_TLS=true
            - KEYCLOAK_TLS_KEYSTORE_FILE=/path/to/keystore.jks
            - KEYCLOAK_TLS_KEYSTORE_PASSWORD_FILE=/path/to/docker/secret
            - KEYCLOAK_TLS_TRUSTSTORE_FILE=/path/to/truststore.jks
            - KEYCLOAK_TLS_TRUSTSTORE_PASSWORD_FILE=/path/to/docker/secret
        volumes:
            - "./path/to/keycloak.jks:/opt/bitnami/keycloak/certs/keystore.jks"
            - "./path/to/keycloak.jks:/opt/bitnami/keycloak/certs/truststore.jks"
        networks:
            default:
                aliases:
                    - keycloak.host.net
        ports:
            - 8083:8083
            - 8443:8443
        secrets:
            - secret

    ui:
        image: <image from AWS ECR>
        labels:
            COMPOSE_PATH: ${PWD}
        depends_on:
            - keycloak
        ports:
            - 8000:8000
        restart: always
        environment:
            - HOSTNAME=ui
            - NAME=UI
            - PORT=8000
            - REGISTER_WITH_KEYCLOAK=True
            - ENVIRONMENT=prod
            - CONSUL_URL=http://consul-server-bootstrap:8500
            - MONGO_URI=mongodb://user:password@mongodb:27017/db
            - QUEUE_HOSTNAME=queue
        secrets:
            - secret

注:Swarm版与Compose版的唯一差异为添加了secrets配置

UI服务关键代码(config.py)

# Landing page
@app.route("/")
def index():
    try:
        current_app.logger.info(f"public/index.html page being displayed")
        return Response(render_template("public/index.html"), mimetype="text/html")
    except exception_with_data as ex:
        return ex.repr_as_dict(), 400

报错信息

2022-07-26T08:21:03.252452 Thread-83  UI  app ERROR Exception on / [GET]
Traceback (most recent call last):
    File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 2070, in wsgi_app
response = self.full_dispatch_request()
    File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 1515, in full_dispatch_request
rv = self.handle_user_exception(e)
    File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 1513, in full_dispatch_request
rv = self.dispatch_request()
    File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 1499, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
    File "/usr/local/lib/python3.9/site-packages/ui/main/config.py", line 209, in index
return Response(render_template("public/index.html"), mimetype="text/html")
    File "/usr/local/lib/python3.9/site-packages/flask/templating.py", line 148, in render_template
ctx.app.jinja_env.get_or_select_template(template_name_or_list),
    File "/usr/local/lib/python3.9/site-packages/jinja2/environment.py", line 1068, in get_or_select_template
return self.get_template(template_name_or_list, parent, globals)
    File "/usr/local/lib/python3.9/site-packages/jinja2/environment.py", line 997, in get_template
return self._load_template(name, globals)
    File "/usr/local/lib/python3.9/site-packages/jinja2/environment.py", line 958, in _load_template
template = self.loader.load(self, name, self.make_globals(globals))
    File "/usr/local/lib/python3.9/site-packages/jinja2/loaders.py", line 125, in load
source, filename, uptodate = self.get_source(environment, name)
    File "/usr/local/lib/python3.9/site-packages/flask/templating.py", line 59, in get_source
return self._get_source_fast(environment, template)
    File "/usr/local/lib/python3.9/site-packages/flask/templating.py", line 95, in _get_source_fast
raise TemplateNotFound(template)
jinja2.exceptions.TemplateNotFound: public/index.html

代码目录结构

ui
    /main
        app.py
        config.py
    /templates
        /admin
            <all admin html pages>
        /public
            index.html
            <all other public html pages>

解决方案

1. 显式指定Flask模板文件夹绝对路径

Flask默认从当前工作目录的templates文件夹加载模板,Swarm部署时可能因工作目录与Compose不一致导致路径解析失败。在Flask应用初始化时,显式指定模板文件夹的绝对路径:

# 在app.py或config.py的Flask初始化代码中修改
app = Flask(__name__, template_folder="/usr/src/app/ui/templates")

替换为容器内templates文件夹的实际绝对路径,可通过docker exec -it <ui-container-id> pwd和ls命令确认。

2. 统一容器工作目录

在Swarm版docker-compose.yml的ui服务中添加working_dir配置,确保与Compose部署时的工作目录一致:

ui:
    # 其他配置不变
    working_dir: /usr/src/app/ui

3. 验证模板文件权限

检查容器内templates文件夹及文件的权限,确保Flask运行用户(通常为python或www-data)拥有读取权限:

docker exec -it <ui-container-id> ls -l /usr/src/app/ui/templates/public

若权限不足,在UI服务的Dockerfile中添加权限设置:

RUN chown -R python:python /usr/src/app/ui/templates

4. 排查环境变量差异

对比Compose与Swarm部署下UI容器的环境变量,确认无影响模板加载的差异(如FLASK_ENV、PYTHONPATH等):

# Compose环境
docker-compose exec ui env
# Swarm环境
docker exec -it <ui-container-id> env

内容的提问来源于stack exchange,提问作者W_ Jonesey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 02:33:28