dmartincy commited on
Commit
db9f671
·
1 Parent(s): 4b75d04

Dockerfile new style

Browse files
Files changed (7) hide show
  1. Dockerfile +82 -72
  2. app.py +10 -0
  3. healthcheck.sh +2 -0
  4. nginx.conf +30 -0
  5. service-config.yml +3 -3
  6. start-container.sh +28 -0
  7. start-services.sh +53 -117
Dockerfile CHANGED
@@ -1,84 +1,94 @@
1
- # Multi-stage build to get pgvector files
2
- FROM pgvector/pgvector:pg16 as pgvector
3
 
4
- # Main image
5
- FROM pspdfkit/ai-assistant:nightly
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
- # Install PostgreSQL 17
8
- RUN echo "https://dl-cdn.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories && \
9
- echo "https://dl-cdn.alpinelinux.org/alpine/edge/community" >> /etc/apk/repositories && \
10
- apk update && \
11
- apk add --no-cache \
12
- postgresql17 \
13
- postgresql17-contrib \
14
- postgresql17-dev
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
- # Get PostgreSQL lib directory
17
- RUN pg_config --pkglibdir > /tmp/pg_libdir
 
18
 
19
- # Create directories
20
- RUN mkdir -p $(cat /tmp/pg_libdir) && \
21
- mkdir -p /usr/share/postgresql17/extension
22
 
23
- # Copy files from pgvector image
24
- COPY --from=pgvector /usr/lib/postgresql/16/lib/vector.so $(cat /tmp/pg_libdir)/
25
- COPY --from=pgvector /usr/share/postgresql/16/extension/vector--0.8.0.sql /usr/share/postgresql17/extension/
26
- COPY --from=pgvector /usr/share/postgresql/16/extension/vector.control /usr/share/postgresql17/extension/
27
 
28
- # Create a modified vector.control file with the correct version
29
- RUN echo "# vector extension" > /usr/share/postgresql17/extension/vector.control && \
30
- echo "comment = 'vector data type and ivfflat and hnsw access methods'" >> /usr/share/postgresql17/extension/vector.control && \
31
- echo "default_version = '0.8.0'" >> /usr/share/postgresql17/extension/vector.control && \
32
- echo "module_pathname = 'vector'" >> /usr/share/postgresql17/extension/vector.control && \
33
- echo "relocatable = false" >> /usr/share/postgresql17/extension/vector.control
34
 
35
- # Environment variables
36
- ENV PGUSER=node \
37
- PGPASSWORD=password \
38
- PGDATABASE=ai_assistant \
39
- PGHOST=localhost \
40
- PGPORT=5432 \
41
- POSTGRES_USER=node \
42
- POSTGRES_PASSWORD=password \
43
- POSTGRES_DB=ai_assistant \
44
- PGDATA=/tmp/postgresql
45
 
46
- # Create necessary directories and set permissions
47
- RUN mkdir -p /run/postgresql && \
48
- chown node:node /run/postgresql && \
49
- chmod 777 /run/postgresql && \
50
- chown -R node:node /usr/share/postgresql17 && \
51
- chown -R node:node $(cat /tmp/pg_libdir) && \
52
- chmod 755 $(cat /tmp/pg_libdir)/vector.so
53
 
54
- # Debug: Show all relevant paths and files
55
- RUN echo "PostgreSQL paths:" && \
56
- pg_config --pkglibdir && \
57
- echo "Files in library directory:" && \
58
- ls -la $(cat /tmp/pg_libdir) | grep vector && \
59
- echo "Files in extension directory:" && \
60
- ls -la /usr/share/postgresql17/extension/ | grep vector
61
 
62
- # Create a wrapper script
63
- RUN echo '#!/bin/sh' > /wrapper.sh && \
64
- echo 'set -e' >> /wrapper.sh && \
65
- echo 'mkdir -p $PGDATA' >> /wrapper.sh && \
66
- echo 'if [ ! -s "$PGDATA/PG_VERSION" ]; then' >> /wrapper.sh && \
67
- echo ' initdb -D $PGDATA' >> /wrapper.sh && \
68
- echo ' echo "host all all all trust" >> $PGDATA/pg_hba.conf' >> /wrapper.sh && \
69
- echo ' echo "listen_addresses = '"'"'*'"'"'" >> $PGDATA/postgresql.conf' >> /wrapper.sh && \
70
- echo ' echo "dynamic_library_path = '"'"'\$libdir'"'"'" >> $PGDATA/postgresql.conf' >> /wrapper.sh && \
71
- echo 'fi' >> /wrapper.sh && \
72
- echo 'pg_ctl -D $PGDATA -o "-c listen_addresses=*" -w start' >> /wrapper.sh && \
73
- echo 'createdb $POSTGRES_DB || true' >> /wrapper.sh && \
74
- echo 'psql -d $POSTGRES_DB -c "SELECT version();"' >> /wrapper.sh && \
75
- echo 'psql -d $POSTGRES_DB -c "SHOW dynamic_library_path;"' >> /wrapper.sh && \
76
- echo 'psql -d $POSTGRES_DB -c "CREATE EXTENSION IF NOT EXISTS vector;"' >> /wrapper.sh && \
77
- echo 'exec /start-services.sh' >> /wrapper.sh && \
78
- chmod +x /wrapper.sh
79
 
80
- # Expose necessary ports
81
- EXPOSE 3000 5432 8080
82
 
83
- # Set the entrypoint
84
- ENTRYPOINT ["/wrapper.sh"]
 
1
+ # Start with PostgreSQL image with pgvector
2
+ FROM pgvector/pgvector:pg16
3
 
4
+ # Set environment variables
5
+ ENV POSTGRES_USER=db-user \
6
+ POSTGRES_PASSWORD=password \
7
+ POSTGRES_DB=ai_assistant \
8
+ POSTGRES_INITDB_ARGS=--data-checksums \
9
+ PGDATA=/var/lib/postgresql/data/pgdata \
10
+ OPENAI_API_KEY="dumy-key" \
11
+ PGUSER=db-user \
12
+ PGPASSWORD=password \
13
+ PGDATABASE=ai_assistant \
14
+ PGHOST=localhost \
15
+ PGPORT=5432 \
16
+ API_AUTH_TOKEN=secret \
17
+ JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2gzhmJ9TDanEzWdP1WG+\n0Ecwbe7f3bv6e5UUpvcT5q68IQJKP47AQdBAnSlFVi4X9SaurbWoXdS6jpmPpk24\nQvitzLNFphHdwjFBelTAOa6taZrSusoFvrtK9x5xsW4zzt/bkpUraNx82Z8MwLwr\nt6HlY7dgO9+xBAabj4t1d2t+0HS8O/ed3CB6T2lj6S8AbLDSEFc9ScO6Uc1XJlSo\nrgyJJSPCpNhSq3AubEZ1wMS1iEtgAzTPRDsQv50qWIbn634HLWxTP/UH6YNJBwzt\n3O6q29kTtjXlMGXCvin37PyX4Jy1IiPFwJm45aWJGKSfVGMDojTJbuUtM+8P9Rrn\nAwIDAQAB\n-----END PUBLIC KEY-----" \
18
+ JWT_ALGORITHM=RS256 \
19
+ DASHBOARD_USERNAME=dashboard \
20
+ DASHBOARD_PASSWORD=secret \
21
+ SECRET_KEY_BASE=secret-key-base
22
 
23
+ # Install required packages including nginx
24
+ RUN apt-get update && \
25
+ apt-get install -y nodejs npm python3-venv python3-pip wget curl unzip nginx-light procps && \
26
+ mkdir -p /tmp/llamafile && \
27
+ cd /tmp/llamafile && \
28
+ wget --no-check-certificate https://github.com/Mozilla-Ocho/llamafile/releases/download/0.8.17/llamafile-0.8.17.zip && \
29
+ unzip llamafile-0.8.17.zip && \
30
+ cp llamafile-0.8.17/bin/llamafiler /usr/local/bin/ && \
31
+ chmod +x /usr/local/bin/llamafiler && \
32
+ rm -rf /tmp/llamafile && \
33
+ # Create all required nginx directories and files with correct ownership and permissions
34
+ mkdir -p /var/run/nginx && \
35
+ mkdir -p /var/lib/nginx/body && \
36
+ mkdir -p /var/lib/nginx/proxy && \
37
+ mkdir -p /var/lib/nginx/fastcgi && \
38
+ mkdir -p /var/lib/nginx/uwsgi && \
39
+ mkdir -p /var/lib/nginx/scgi && \
40
+ mkdir -p /var/log/nginx && \
41
+ touch /var/log/nginx/access.log && \
42
+ touch /var/log/nginx/error.log && \
43
+ chmod 755 /var/lib && \
44
+ chmod 755 /var/log && \
45
+ chmod 755 /var/run && \
46
+ chown -R postgres:postgres /var/run/nginx /var/log/nginx /var/lib/nginx && \
47
+ chmod -R 755 /var/lib/nginx /var/log/nginx && \
48
+ chmod 777 /var/run/nginx && \
49
+ chmod 666 /var/log/nginx/access.log && \
50
+ chmod 666 /var/log/nginx/error.log
51
 
52
+ # Create and activate virtual environment
53
+ RUN python3 -m venv /venv
54
+ ENV PATH="/venv/bin:$PATH"
55
 
56
+ # Install Flask in the virtual environment
57
+ RUN /venv/bin/pip install flask
 
58
 
59
+ # Copy the Flask app
60
+ COPY app.py /app.py
61
+ COPY service-config.yml /service-config.yml
 
62
 
63
+ # Create a healthcheck script
64
+ COPY healthcheck.sh /usr/local/bin/healthcheck.sh
65
+ RUN chmod +x /usr/local/bin/healthcheck.sh
 
 
 
66
 
67
+ # Pull and extract the AI assistant image
68
+ COPY --from=pspdfkit/ai-assistant:nightly / /ai-assistant
69
+ COPY service-config.yml /ai-assistant/base/config/service-config.yml
70
+
71
+ # Copy and prepare start script
72
+ COPY start-services.sh /start-services.sh
73
+ RUN chmod +x /start-services.sh
74
+
75
+ # Copy Nginx configuration
76
+ COPY nginx.conf /etc/nginx/nginx.conf
77
 
78
+ # Create base directory and download models during build
79
+ RUN mkdir -p /base && \
80
+ wget -q https://huggingface.co/bartowski/gemma-2-2b-it-GGUF/resolve/main/gemma-2-2b-it-Q4_K_M.gguf -O /base/gemma-2b.gguf && \
81
+ wget -q https://huggingface.co/leliuga/all-MiniLM-L6-v2-GGUF/resolve/main/all-MiniLM-L6-v2.F16.gguf -O /base/embeddings.gguf && \
82
+ chown -R postgres:postgres /base
 
 
83
 
84
+ # Expose ports
85
+ EXPOSE 5432 7860 4000 8080 8081 8082
 
 
 
 
 
86
 
87
+ # Add a healthcheck
88
+ HEALTHCHECK --interval=3s --timeout=3s --retries=10 \
89
+ CMD ["healthcheck.sh"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
 
91
+ USER postgres
 
92
 
93
+ # Start PostgreSQL, Flask, and AI assistant
94
+ CMD ["sh", "-c", "/venv/bin/flask run --host=0.0.0.0 --port=7860 & docker-entrypoint.sh postgres & /start-services.sh"]
app.py ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask
2
+
3
+ app = Flask(__name__)
4
+
5
+ @app.route('/')
6
+ def home():
7
+ return "PostgreSQL is running with Flask!"
8
+
9
+ if __name__ == '__main__':
10
+ app.run(host='0.0.0.0', port=7860)
healthcheck.sh ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ #!/bin/bash
2
+ pg_isready -U db-user -d ai_assistant
nginx.conf ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ pid /var/run/nginx/nginx.pid;
2
+
3
+ events {
4
+ worker_connections 1024;
5
+ }
6
+
7
+ http {
8
+ include /etc/nginx/mime.types;
9
+ default_type application/octet-stream;
10
+
11
+ server {
12
+ listen 8080;
13
+
14
+ location /v1/embeddings {
15
+ proxy_pass http://127.0.0.1:8081;
16
+ proxy_set_header Host $host;
17
+ proxy_set_header X-Real-IP $remote_addr;
18
+ proxy_read_timeout 300;
19
+ proxy_connect_timeout 300;
20
+ }
21
+
22
+ location /v1 {
23
+ proxy_pass http://127.0.0.1:8082;
24
+ proxy_set_header Host $host;
25
+ proxy_set_header X-Real-IP $remote_addr;
26
+ proxy_read_timeout 300;
27
+ proxy_connect_timeout 300;
28
+ }
29
+ }
30
+ }
service-config.yml CHANGED
@@ -4,17 +4,17 @@ aiServices:
4
  chat:
5
  provider:
6
  name: 'openai-compat'
7
- baseUrl: http://localhost:8080/v1
8
  model: 'gemma-2b'
9
  textEmbeddings:
10
  provider:
11
  name: 'openai-compat'
12
- baseUrl: http://localhost:8080/v1
13
  model: 'all-MiniLM-L6-v2'
14
  headless:
15
  - provider:
16
  name: 'openai-compat'
17
- baseUrl: http://localhost:8080/v1
18
  model:
19
  name: 'gemma-2b'
20
  id: 'gemma-2b'
 
4
  chat:
5
  provider:
6
  name: 'openai-compat'
7
+ baseUrl: http://127.0.0.1:8080/v1
8
  model: 'gemma-2b'
9
  textEmbeddings:
10
  provider:
11
  name: 'openai-compat'
12
+ baseUrl: http://127.0.0.1:8080/v1
13
  model: 'all-MiniLM-L6-v2'
14
  headless:
15
  - provider:
16
  name: 'openai-compat'
17
+ baseUrl: http://127.0.0.1:8080/v1
18
  model:
19
  name: 'gemma-2b'
20
  id: 'gemma-2b'
start-container.sh ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+
3
+ # Start nginx
4
+ nginx
5
+
6
+ # Start llamafiler instances
7
+ /usr/local/bin/llamafiler --model /base/gemma-2b.gguf --listen 0.0.0.0:8082 &
8
+ GEMMA_PID=$!
9
+
10
+ /usr/local/bin/llamafiler --model /base/embeddings.gguf --listen 0.0.0.0:8081 &
11
+ EMBEDDINGS_PID=$!
12
+
13
+ # Wait for services
14
+ until curl -s --fail -X POST http://localhost:8080/v1/chat/completions \
15
+ -H "Content-Type: application/json" \
16
+ -d '{"model": "gemma-2b", "messages":[{"role":"user","content":"hi"}]}' >/dev/null 2>&1 && \
17
+ curl -s --fail -X POST http://localhost:8080/v1/embeddings \
18
+ -H "Content-Type: application/json" \
19
+ -d '{"input":"test"}' >/dev/null 2>&1
20
+ do
21
+ sleep 1
22
+ echo "Waiting for services..."
23
+ done
24
+
25
+ echo 'Services are ready!'
26
+
27
+ # Start the main application
28
+ exec ./bin/entrypoint.sh node app/main.bundle.js
start-services.sh CHANGED
@@ -1,136 +1,72 @@
1
  #!/bin/sh
2
 
3
- # Create directories if they don't exist
4
- mkdir -p /data/models
5
- mkdir -p /var/log/llamafiler
6
-
7
- # Install required packages if not already installed
8
- if ! command -v wget >/dev/null 2>&1; then
9
- echo "Installing dependencies..."
10
- apk add --no-cache wget curl nginx \
11
- build-base \
12
- gcc \
13
- musl-dev \
14
- linux-headers \
15
- python3 \
16
- py3-pip \
17
- git \
18
- make
19
- fi
20
 
21
- # Check if models exist in persistent storage, download only if needed
22
- if [ ! -f "/data/models/gemma-2b.gguf" ]; then
23
- echo "Downloading Gemma model..."
24
- wget -q https://huggingface.co/bartowski/gemma-2-2b-it-GGUF/resolve/main/gemma-2-2b-it-Q4_K_M.gguf -O /data/models/gemma-2b.gguf
 
 
25
  fi
26
 
27
- if [ ! -f "/data/models/embeddings.gguf" ]; then
28
- echo "Downloading embeddings model..."
29
- wget -q https://huggingface.co/leliuga/all-MiniLM-L6-v2-GGUF/resolve/main/all-MiniLM-L6-v2.F16.gguf -O /data/models/embeddings.gguf
 
30
  fi
31
 
32
- # Install llamafiler only if not already installed
33
- if [ ! -f "/usr/local/bin/llamafiler" ]; then
34
- echo "Building llamafiler from source..."
35
- mkdir -p /tmp/llamafile
36
- cd /tmp/llamafile
37
- wget --no-check-certificate https://github.com/Mozilla-Ocho/llamafile/releases/download/0.8.17/llamafile-0.8.17.zip
38
- unzip llamafile-0.8.17.zip
39
-
40
- if [ -f llamafile-0.8.17/bin/llamafiler ]; then
41
- echo "Installing llamafiler..."
42
- cp llamafile-0.8.17/bin/llamafiler /usr/local/bin/
43
- chmod +x /usr/local/bin/llamafiler
44
- else
45
- echo "ERROR: Llamafiler binary not found!"
46
- exit 1
47
- fi
48
- cd - # Return to previous directory
49
- rm -rf /tmp/llamafile
50
- fi
51
 
52
- # Setup nginx reverse proxy
53
- cat > /etc/nginx/nginx.conf <<EOF
54
- events { worker_connections 1024; }
55
- http {
56
- server {
57
- listen 8080;
58
- location /v1/embeddings {
59
- proxy_pass http://localhost:8081;
60
- proxy_read_timeout 300;
61
- proxy_connect_timeout 300;
62
- proxy_send_timeout 300;
63
- }
64
- location /v1 {
65
- proxy_pass http://localhost:8082;
66
- proxy_read_timeout 300;
67
- proxy_connect_timeout 300;
68
- proxy_send_timeout 300;
69
- }
70
- }
71
- }
72
- EOF
73
 
74
- # Start nginx
75
- echo "Starting nginx..."
76
- nginx
77
- echo "Nginx started"
78
 
79
- # Start llamafiler instances with logging
80
- echo "Starting llamafiler for Gemma model..."
81
- /usr/local/bin/llamafiler --model /data/models/gemma-2b.gguf --listen 0.0.0.0:8082 &
82
  GEMMA_PID=$!
83
- echo "Gemma PID: $GEMMA_PID"
84
 
85
- echo "Starting llamafiler for embeddings model..."
86
- /usr/local/bin/llamafiler --model /data/models/embeddings.gguf --listen 0.0.0.0:8081 &
87
  EMBEDDINGS_PID=$!
88
- echo "Embeddings PID: $EMBEDDINGS_PID"
89
-
90
- # More robust health check
91
- echo 'Waiting for servers to start...'
92
- TIMEOUT=300 # 5 minutes timeout
93
- START_TIME=$(date +%s)
94
 
95
- while true; do
96
- CURRENT_TIME=$(date +%s)
97
- ELAPSED_TIME=$((CURRENT_TIME - START_TIME))
98
-
99
- if [ $ELAPSED_TIME -gt $TIMEOUT ]; then
100
- echo "Timeout waiting for services to start"
101
- exit 1
102
- fi
103
 
104
- # Check if processes are still running
105
- if ! kill -0 $GEMMA_PID 2>/dev/null; then
106
- echo "Gemma model process died."
107
- exit 1
108
- fi
109
- if ! kill -0 $EMBEDDINGS_PID 2>/dev/null; then
110
- echo "Embeddings model process died."
111
- exit 1
112
- fi
113
-
114
- # Try to contact both services
115
- if curl -s --fail -X POST http://localhost:8080/v1/chat/completions \
116
- -H "Content-Type: application/json" \
117
- -d '{"model": "gemma-2b", "messages":[{"role":"user","content":"hi"}]}' >/dev/null 2>&1 && \
118
- curl -s --fail -X POST http://localhost:8080/v1/embeddings \
119
- -H "Content-Type: application/json" \
120
- -d '{"input":"test"}' >/dev/null 2>&1; then
121
- break # Both services are responding
122
- fi
123
-
124
- sleep 5
125
- echo "Waiting for services... (Elapsed time: ${ELAPSED_TIME}s)"
126
  done
127
 
128
  echo 'Servers are ready!'
129
 
130
- # Start the main application
131
- if [ -f "./bin/entrypoint.sh" ]; then
132
- exec ./bin/entrypoint.sh node app/main.bundle.js
133
- else
134
- echo "Warning: entrypoint.sh not found, starting node directly"
135
- exec node app/main.bundle.js
136
- fi
 
1
  #!/bin/sh
2
 
3
+ # Wait for PostgreSQL to be ready
4
+ until pg_isready -h localhost -U db-user
5
+ do
6
+ echo "Waiting for PostgreSQL to be ready..."
7
+ sleep 1
8
+ done
 
 
 
 
 
 
 
 
 
 
 
9
 
10
+ # Start nginx with explicit configuration
11
+ echo "Starting nginx..."
12
+ /usr/sbin/nginx -c /etc/nginx/nginx.conf
13
+ if [ $? -ne 0 ]; then
14
+ echo "Failed to start nginx"
15
+ exit 1
16
  fi
17
 
18
+ # Verify nginx is running
19
+ if ! ps aux | grep nginx | grep -v grep > /dev/null; then
20
+ echo "Nginx failed to start"
21
+ exit 1
22
  fi
23
 
24
+ echo "Nginx started successfully"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
+ # Start llamafiler instances
27
+ echo "Starting llamafiler services..."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
+ # Create TMPDIR if it doesn't exist
30
+ mkdir -p /tmp/llamafiler
 
 
31
 
32
+ echo "Starting chat model..."
33
+ TMPDIR=/tmp/llamafiler /usr/local/bin/llamafiler --model /base/gemma-2b.gguf --listen 0.0.0.0:8082 &
 
34
  GEMMA_PID=$!
 
35
 
36
+ echo "Starting embedding model..."
37
+ TMPDIR=/tmp/llamafiler /usr/local/bin/llamafiler --model /base/embeddings.gguf --listen 0.0.0.0:8081 &
38
  EMBEDDINGS_PID=$!
 
 
 
 
 
 
39
 
40
+ # Wait for the models to start loading
41
+ echo "Waiting for initial model loading..."
42
+ sleep 15 # Give more time for the models to load into memory
 
 
 
 
 
43
 
44
+ echo 'Waiting for servers to start...'
45
+ until curl -s --fail -X POST http://localhost:8080/v1/chat/completions \
46
+ -H "Content-Type: application/json" \
47
+ -d '{"model": "gemma-2b", "messages":[{"role":"user","content":"hi"}]}' >/dev/null 2>&1 && \
48
+ curl -s --fail -X POST http://localhost:8080/v1/embeddings \
49
+ -H "Content-Type: application/json" \
50
+ -d '{"input":"test"}' >/dev/null 2>&1
51
+ do
52
+ # Check if processes are still running
53
+ if ! kill -0 $GEMMA_PID 2>/dev/null; then
54
+ echo "Gemma model process died."
55
+ exit 1
56
+ fi
57
+ if ! kill -0 $EMBEDDINGS_PID 2>/dev/null; then
58
+ echo "Embeddings model process died."
59
+ exit 1
60
+ fi
61
+
62
+ sleep 2
63
+ echo "Waiting for services... (PIDs: Gemma=$GEMMA_PID, Embeddings=$EMBEDDINGS_PID)"
 
 
64
  done
65
 
66
  echo 'Servers are ready!'
67
 
68
+ # Start the AI assistant application from the correct directory
69
+ cd /ai-assistant/base && \
70
+ NODE_PATH=/ai-assistant/base/node_modules \
71
+ PATH=/ai-assistant/base/node_modules/.bin:/ai-assistant/base/bin:$PATH \
72
+ exec /ai-assistant/base/bin/entrypoint.sh node app/main.bundle.js