Repository navigation
Expand file tree
/
Copy pathDockerfile
More file actions
419 lines (348 loc) · 18.9 KB
/
Copy pathDockerfile
File metadata and controls
419 lines (348 loc) · 18.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
# ====================
# Stage 1: Build nsjail from source
# ====================
FROM debian:bookworm-slim AS nsjail-builder
ENV DEBIAN_FRONTEND=noninteractive
# Pin NsJail 4.0 source with the in-process NSTUN policy backend.
ENV NSJAIL_COMMIT=388b9655a696e88df3185d09e36c0378a8532904
RUN apt-get update && apt-get install -y --no-install-recommends \
git gcc g++ make pkg-config bison flex \
libprotobuf-dev protobuf-compiler libnl-route-3-dev ca-certificates \
&& rm -rf /var/lib/apt/lists/*
COPY docker/nsjail/nstun-bounded-memory.patch /tmp/nstun-bounded-memory.patch
RUN git clone https://github.com/google/nsjail.git /tmp/nsjail && \
cd /tmp/nsjail && git checkout "${NSJAIL_COMMIT}" && \
git apply --check /tmp/nstun-bounded-memory.patch && \
git apply /tmp/nstun-bounded-memory.patch && \
git submodule update --init --recursive && \
make -j"$(nproc)" && \
install -m 0755 nsjail /usr/local/bin/nsjail && \
rm -rf /tmp/nsjail
COPY docker/loop-device-sync.cc /tmp/loop-device-sync.cc
RUN g++ -std=c++20 -O2 -Wall -Wextra -Werror -Wformat=2 \
-D_FORTIFY_SOURCE=2 -fstack-protector-strong -fPIE -pie \
-Wl,-z,relro,-z,now \
/tmp/loop-device-sync.cc -o /usr/local/bin/tracecat-loop-device-sync && \
strip /usr/local/bin/tracecat-loop-device-sync && \
rm /tmp/loop-device-sync.cc
# ====================
# Stage 2: Create minimal sandbox rootfs
# ====================
FROM node:22.13.1-slim AS node-bin
FROM python:3.12-slim-bookworm AS sandbox-rootfs
ARG TARGETARCH
ARG DUCKDB_VERSION=1.4.3
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates curl wget jq iputils-ping git openssh-client squashfs-tools \
&& rm -rf /var/lib/apt/lists/*
# This rootfs is shared by run_python and agent sandboxes; CLI additions here
# are intentionally available to both. DuckDB is not available from Bookworm
# apt, so install the official release binary, verify it, preinstall extensions,
# and wrap the CLI to load those extensions on each invocation.
#
# Both the CLI binary and every extension are pinned by sha256: extensions are
# downloaded from the version-pinned repository, verified, and installed from
# local files (DuckDB still validates the signed extensions on load) instead of
# resolving "latest for this version" via a bare INSTALL.
RUN set -eu; \
arch="${TARGETARCH:-$(dpkg --print-architecture)}"; \
case "${arch}" in \
amd64) \
platform="linux_amd64"; \
duckdb_sha256="c479794045d094058d3092e404e696508d6310b5d234a8c1945b745678f09d8d"; \
ext_shas="json:35144e27f6635f4934a715fc721bd26e74520852cf92d920c4e7a0c8729c3e8b httpfs:786b8e1aea9b49bb3072dba9f553d84c1a9aa042e9351124bb3ec2753b221227 inet:07430f6c1ad5a03e6fcbf153c2690765bfd445c9cab40927011105b75bef4a31 fts:ed5e639aee5070dee0b58f60322393c1861c5c82d57164192065ef758f1371b5"; \
;; \
arm64) \
platform="linux_arm64"; \
duckdb_sha256="c709eb3efc74a609af4b92bc885c509a1bd21ddfa71ea1e717420d4dd9fc121b"; \
ext_shas="json:0dbe43e05f23bd9c9e4df30a80a201b05cac5c7a0a1709143195451a526fe132 httpfs:662e96fe6c39724be7977649314b92eadf3a9a0c6127c3a3e96611480d6ad04b inet:c48932d9060053e570c76a99169709ad4e52f1ba5cb1a1468e8b1eb00899361a fts:0966c1c9cfb798845b53117ea7f3fd7210fb545d63b1273963075a4b56311a5a"; \
;; \
*) echo "Unsupported DuckDB CLI architecture: ${arch}" >&2; exit 1 ;; \
esac; \
curl -fsSL "https://github.com/duckdb/duckdb/releases/download/v${DUCKDB_VERSION}/duckdb_cli-linux-${arch}.gz" -o /tmp/duckdb.gz; \
echo "${duckdb_sha256} /tmp/duckdb.gz" | sha256sum -c -; \
gunzip /tmp/duckdb.gz; \
install -m 0755 /tmp/duckdb /usr/local/bin/duckdb.real; \
rm -f /tmp/duckdb; \
mkdir -p /usr/local/lib/duckdb/extensions /usr/local/share/duckdb /tmp/ddbext; \
install_args=""; \
for spec in ${ext_shas}; do \
name="${spec%%:*}"; sha="${spec##*:}"; \
curl -fsSL "https://extensions.duckdb.org/v${DUCKDB_VERSION}/${platform}/${name}.duckdb_extension.gz" -o "/tmp/ddbext/${name}.duckdb_extension.gz"; \
echo "${sha} /tmp/ddbext/${name}.duckdb_extension.gz" | sha256sum -c -; \
gunzip "/tmp/ddbext/${name}.duckdb_extension.gz"; \
install_args="${install_args} INSTALL '/tmp/ddbext/${name}.duckdb_extension';"; \
done; \
/usr/local/bin/duckdb.real -c "SET extension_directory = '/usr/local/lib/duckdb/extensions';${install_args}"; \
rm -rf /tmp/ddbext; \
printf '%s\n' \
"SET extension_directory = '/usr/local/lib/duckdb/extensions';" \
"LOAD json;" \
"LOAD httpfs;" \
"LOAD inet;" \
"LOAD fts;" \
> /usr/local/share/duckdb/tracecat-init.sql; \
printf '%s\n' \
'#!/bin/sh' \
'exec /usr/local/bin/duckdb.real -init /usr/local/share/duckdb/tracecat-init.sql "$@"' \
> /usr/local/bin/duckdb; \
chmod 0755 /usr/local/bin/duckdb; \
jq --version; \
duckdb --version; \
test "$(duckdb -csv -noheader -c "SELECT count(*) FROM duckdb_extensions() WHERE extension_name IN ('json', 'httpfs', 'inet', 'fts') AND installed AND loaded;")" = "4"
COPY --from=ghcr.io/astral-sh/uv:0.9.15 /uv /usr/local/bin/uv
COPY --from=ghcr.io/astral-sh/uv:0.9.15 /uvx /usr/local/bin/uvx
COPY --from=node-bin /usr/local/bin/node /usr/local/bin/node
COPY --from=node-bin /usr/local/lib/node_modules /usr/local/lib/node_modules
RUN ln -s ../lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm && \
ln -s ../lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx
RUN useradd -m -u 1000 sandbox && \
mkdir -p /workspace /work /cache /packages /home/sandbox && \
chown sandbox:sandbox /workspace /work /cache /packages /home/sandbox
ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
# ====================
# Stage 3: Shared base with runtime dependencies
# ====================
FROM ghcr.io/astral-sh/uv:0.9.15-python3.12-bookworm-slim AS base
ENV HOST=0.0.0.0 PORT=8000
# Copy nsjail binary
COPY --from=nsjail-builder /usr/local/bin/nsjail /usr/local/bin/nsjail
# Copy Node.js + npx for in-process MCP command servers (stdio)
COPY --from=node-bin /usr/local/bin/node /usr/local/bin/node
COPY --from=node-bin /usr/local/lib/node_modules /usr/local/lib/node_modules
RUN ln -s ../lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm && \
ln -s ../lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx
# Install runtime packages
RUN apt-get update && apt-get install -y --no-install-recommends \
acl git openssh-client xmlsec1 libmagic1 curl ca-certificates jq \
libnl-route-3-200 libprotobuf32 libcap2-bin util-linux \
squashfs-tools \
&& apt-get -y upgrade \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# DuckDB (CLI binary + extensions) is stored as a single physical copy inside
# the sandbox rootfs (copied in below). The executor host — including the
# in-process DuckDB Python package used by core.duckdb.execute_sql — reaches it
# through symlinks created right after that copy, so the large extension
# binaries are not stored twice in the image. The Python package loads
# extensions from this directory instead of autoinstalling over the network.
ENV TRACECAT__DUCKDB_EXTENSION_DIRECTORY=/usr/local/lib/duckdb/extensions
# Copy sandbox rootfs. --chmod=755 normalizes every copied path and drops
# setuid/setgid bits at copy time; a recursive chmod after the copy would store
# a second full copy of the rootfs in its own layer.
COPY --from=sandbox-rootfs --chmod=755 /usr /var/lib/tracecat/sandbox-rootfs/usr
# Expose the single rootfs DuckDB copy to the executor host via symlinks, so the
# host CLI and the in-process DuckDB Python package work without a second copy
# of the extension binaries. The nsjail sandbox uses its own (physical) rootfs
# copy and is unaffected by these host-side links.
RUN ln -s /var/lib/tracecat/sandbox-rootfs/usr/local/lib/duckdb /usr/local/lib/duckdb && \
ln -s /var/lib/tracecat/sandbox-rootfs/usr/local/share/duckdb /usr/local/share/duckdb && \
ln -s /var/lib/tracecat/sandbox-rootfs/usr/local/bin/duckdb.real /usr/local/bin/duckdb.real && \
ln -s /var/lib/tracecat/sandbox-rootfs/usr/local/bin/duckdb /usr/local/bin/duckdb && \
jq --version && duckdb --version
COPY --from=sandbox-rootfs --chmod=755 /lib /var/lib/tracecat/sandbox-rootfs/lib
COPY --from=sandbox-rootfs --chmod=755 /bin /var/lib/tracecat/sandbox-rootfs/bin
COPY --from=sandbox-rootfs --chmod=755 /sbin /var/lib/tracecat/sandbox-rootfs/sbin
COPY --from=sandbox-rootfs --chmod=755 /etc/passwd /var/lib/tracecat/sandbox-rootfs/etc/passwd
COPY --from=sandbox-rootfs --chmod=755 /etc/group /var/lib/tracecat/sandbox-rootfs/etc/group
COPY --from=sandbox-rootfs --chmod=755 /etc/ssl /var/lib/tracecat/sandbox-rootfs/etc/ssl
COPY --from=sandbox-rootfs --chmod=755 /etc/ca-certificates /var/lib/tracecat/sandbox-rootfs/etc/ca-certificates
RUN install -m 0644 /dev/null /var/lib/tracecat/sandbox-rootfs/etc/resolv.conf && \
install -m 0644 /dev/null /var/lib/tracecat/sandbox-rootfs/etc/hosts && \
install -m 0644 /dev/null /var/lib/tracecat/sandbox-rootfs/etc/nsswitch.conf
# Handle lib64 for amd64
RUN if [ -d /var/lib/tracecat/sandbox-rootfs/lib64 ] || [ "$(uname -m)" = "x86_64" ]; then \
mkdir -p /var/lib/tracecat/sandbox-rootfs/lib64 && \
cp -a /lib64/. /var/lib/tracecat/sandbox-rootfs/lib64/ 2>/dev/null || true; \
fi
# Create sandbox directories
RUN mkdir -p /var/lib/tracecat/sandbox-rootfs/tmp \
/var/lib/tracecat/sandbox-rootfs/proc \
/var/lib/tracecat/sandbox-rootfs/dev \
/var/lib/tracecat/sandbox-rootfs/work \
/var/lib/tracecat/sandbox-rootfs/cache \
/var/lib/tracecat/sandbox-rootfs/packages \
/var/lib/tracecat/sandbox-rootfs/home/sandbox \
/var/lib/tracecat/sandbox-cache/packages \
/var/lib/tracecat/sandbox-cache/uv-cache && \
chown -R 1000:1000 /var/lib/tracecat/sandbox-rootfs/work \
/var/lib/tracecat/sandbox-rootfs/cache \
/var/lib/tracecat/sandbox-rootfs/packages \
/var/lib/tracecat/sandbox-rootfs/home/sandbox && \
chmod 1777 /var/lib/tracecat/sandbox-rootfs/tmp
# Create apiuser for the non-root runtime.
RUN groupadd -g 1001 apiuser && useradd -m -u 1001 -g apiuser apiuser && \
mkdir -p /home/apiuser/.cache/uv /home/apiuser/.cache/s3 /home/apiuser/.cache/tmp /home/apiuser/.local/bin && \
chown -R apiuser:apiuser /home/apiuser
# Create MCP socket directory for apiuser
RUN mkdir -p /var/run/tracecat && chown 1001:1001 /var/run/tracecat
# Allow the non-root executor process to invoke mount/umount and synchronize
# kernel-confirmed loop nodes when the runtime grants the needed bounding
# capabilities. The fixed-purpose sync helper cannot create arbitrary devices.
COPY --from=nsjail-builder /usr/local/bin/tracecat-loop-device-sync /usr/local/bin/tracecat-loop-device-sync
RUN chmod u-s /usr/bin/mount /usr/bin/umount && \
setcap cap_sys_admin,cap_dac_override+ep /usr/bin/mount && \
setcap cap_sys_admin,cap_dac_override+ep /usr/bin/umount && \
setcap cap_mknod,cap_dac_override,cap_setuid+ep /usr/local/bin/tracecat-loop-device-sync
WORKDIR /app
# ====================
# Stage 4: Fetch workspace-chat copilot skills
# ====================
FROM base AS plugin-skills
ARG TRACECAT_PLUGINS_REF=a41bbdbb4ad3d5e5cf15eda8a6e3d8c3018393cd
ARG TRACECAT_PLUGINS_ARCHIVE_SHA256=e3c67cc4ae2cf0319fe4455b507d0500999600de4f52d93c7c673d9b00a2096f
# Pinned to a tracecat-plugins commit on main. Bump both values when the
# vendored skills change so the trusted payload is verified before extraction.
RUN set -eux; \
mkdir -p /skills /tmp/tracecat-plugins; \
curl -fsSL \
"https://github.com/TracecatHQ/tracecat-plugins/archive/${TRACECAT_PLUGINS_REF}.tar.gz" \
-o /tmp/tracecat-plugins.tar.gz; \
echo "${TRACECAT_PLUGINS_ARCHIVE_SHA256} /tmp/tracecat-plugins.tar.gz" \
| sha256sum -c -; \
tar -xzf /tmp/tracecat-plugins.tar.gz \
-C /tmp/tracecat-plugins \
--strip-components=1; \
for skill in \
tracecat-workspace-chat \
tracecat-automation-best-practices \
tracecat-slackbot-best-practices; do \
source="/tmp/tracecat-plugins/plugins/tracecat/skills/${skill}"; \
test -d "${source}"; \
test -f "${source}/SKILL.md"; \
cp -a "${source}" /skills/; \
done; \
test "$(find /skills -mindepth 1 -maxdepth 1 -type d | wc -l)" -eq 3; \
rm -rf /tmp/tracecat-plugins /tmp/tracecat-plugins.tar.gz
# Workspace Chat reads platform guidance from docs built from this same commit.
# Keep source structure so docs.json and MDX imports remain useful navigation.
COPY ./docs /tmp/tracecat-docs
RUN set -eux; \
docs_source=/tmp/tracecat-docs; \
docs_target=/skills/tracecat-workspace-chat/references/docs; \
mkdir -p "${docs_target}"; \
cd "${docs_source}"; \
find . -type f \( \
-name '*.mdx' -o \
-name 'docs.json' -o \
-path './automations/core-actions/_examples.yaml' -o \
-path './automations/core-actions/_manifest.yaml' \
\) -exec cp --parents '{}' "${docs_target}" \;; \
source_count="$(find "${docs_source}" -type f \( \
-name '*.mdx' -o \
-name 'docs.json' -o \
-path '*/automations/core-actions/_examples.yaml' -o \
-path '*/automations/core-actions/_manifest.yaml' \
\) | wc -l)"; \
target_count="$(find "${docs_target}" -type f | wc -l)"; \
test "${source_count}" -gt 0; \
test "${source_count}" -eq "${target_count}"; \
test -f "${docs_target}/docs.json"; \
test -f "${docs_target}/agents/workspace-chat.mdx"; \
test -f "${docs_target}/automations/workflows.mdx"; \
test -f "${docs_target}/automations/core-actions/_examples.yaml"; \
test -f "${docs_target}/automations/core-actions/_manifest.yaml"; \
test -z "$(find "${docs_target}" -type f \( \
-iname '*.gif' -o -iname '*.jpeg' -o -iname '*.jpg' -o \
-iname '*.png' -o -iname '*.svg' -o -iname '*.webp' \
\) -print -quit)"; \
rm -rf "${docs_source}"
# ====================
# Stage 5: Prepare development source without bundled docs
# ====================
FROM base AS development-source
COPY . /source/
RUN rm -rf /source/docs
# ====================
# Stage 6: Development app
# ====================
FROM base AS development-app
ENV TMPDIR="/home/apiuser/.cache/tmp" TEMP="/home/apiuser/.cache/tmp" TMP="/home/apiuser/.cache/tmp"
# Set sandbox cache permissions for apiuser
RUN chown -R 1001:1001 /var/lib/tracecat/sandbox-cache && \
chmod -R 755 /var/lib/tracecat/sandbox-cache
RUN chown apiuser:apiuser /app
# Switch to the non-root user used in production before installing, so /app
# never needs a recursive chown (which would store a second copy of the venv).
USER apiuser
# Install third-party dependencies only, so this layer is reused until uv.lock or
# pyproject.toml change. --frozen because uv cannot validate the lockfile
# without workspace member sources; the sync below runs with --locked.
RUN --mount=type=cache,target=/home/apiuser/.cache/uv,uid=1001,gid=1001 \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --frozen --no-install-workspace --no-dev --no-editable
COPY --from=development-source --chown=apiuser:apiuser /source/ /app/
COPY --from=plugin-skills --chown=apiuser:apiuser /skills/ /var/lib/tracecat/copilot-skills/
RUN --mount=type=cache,target=/home/apiuser/.cache/uv,uid=1001,gid=1001 uv sync --locked --no-dev
ENV PATH="/app/.venv/bin:$PATH"
ENV PYTHONPATH="/home/apiuser/.local"
RUN mkdir -p /home/apiuser/.local/bin && ln -s $(which uv) /home/apiuser/.local/bin/uv
EXPOSE $PORT
CMD ["sh", "-c", "python3 -m uvicorn tracecat.api.app:app --host $HOST --port $PORT --reload"]
# ====================
# Stage 7: Development registry manifest
# ====================
FROM development-app AS development-registry-manifest
RUN /app/.venv/bin/python -m tracecat.registry.sync.prebuild
# ====================
# Stage 8: Development target
# ====================
FROM development-app AS development
# Carry only the generated builtin registry metadata in dev images too, so local
# cluster startup can update the DB without rediscovering actions on the hot path.
COPY --from=development-registry-manifest --chown=apiuser:apiuser /app/.registry-artifacts /app/.registry-artifacts
# ====================
# Stage 9: Test target (development + pytest)
# ====================
FROM development AS test
# Install test dependencies
RUN --mount=type=cache,target=/home/apiuser/.cache/uv,uid=1001,gid=1001 uv sync --frozen --group dev
CMD ["python", "-m", "pytest"]
# ====================
# Stage 10: Production app
# ====================
FROM base AS production-app
ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy
# Set sandbox cache permissions for apiuser
RUN chown -R 1001:1001 /var/lib/tracecat/sandbox-cache && \
chmod -R 755 /var/lib/tracecat/sandbox-cache
ENV PYTHONUSERBASE="/home/apiuser/.local"
ENV UV_CACHE_DIR="/home/apiuser/.cache/uv"
ENV PYTHONPATH="/home/apiuser/.local"
ENV PATH="/home/apiuser/.local/bin:/usr/local/bin:/usr/bin:/bin"
ENV TMPDIR="/home/apiuser/.cache/tmp" TEMP="/home/apiuser/.cache/tmp" TMP="/home/apiuser/.cache/tmp"
RUN mkdir -p /app/.scripts && chown -R apiuser:apiuser /app
# Switch to non-root user
USER apiuser
# Install third-party dependencies only, so this layer is reused until uv.lock or
# pyproject.toml change. --frozen because uv cannot validate the lockfile
# without workspace member sources; the final sync below runs with --locked.
RUN --mount=type=cache,target=/home/apiuser/.cache/uv,uid=1001,gid=1001 \
--mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv sync --frozen --no-install-workspace --no-dev --no-editable
COPY --chown=apiuser:apiuser ./tracecat /app/tracecat
COPY --chown=apiuser:apiuser ./packages /app/packages
COPY --from=plugin-skills --chown=apiuser:apiuser /skills/ /var/lib/tracecat/copilot-skills/
COPY --chown=apiuser:apiuser ./pyproject.toml ./uv.lock ./.python-version ./README.md ./LICENSE ./alembic.ini /app/
COPY --chown=apiuser:apiuser ./alembic /app/alembic
RUN --mount=type=cache,target=/home/apiuser/.cache/uv,uid=1001,gid=1001 \
uv sync --locked --no-dev --no-editable
ENV PATH="/app/.venv/bin:/home/apiuser/.local/bin:/usr/local/bin:/usr/bin:/bin"
RUN ln -sf $(which uv) /home/apiuser/.local/bin/uv
# ====================
# Stage 11: Production registry manifest
# ====================
FROM production-app AS registry-manifest
RUN /app/.venv/bin/python -m tracecat.registry.sync.prebuild
# ====================
# Stage 12: Production target
# ====================
FROM production-app AS production
# Carry only the generated builtin registry metadata in the image so platform
# registry startup can update the DB without rediscovering actions on the hot path.
COPY --from=registry-manifest --chown=apiuser:apiuser /app/.registry-artifacts /app/.registry-artifacts
# Verification
RUN nsjail --help > /dev/null 2>&1 && echo "nsjail available"
EXPOSE $PORT
CMD ["sh", "-c", "python3 -m uvicorn tracecat.api.app:app --host $HOST --port $PORT"]