Compare commits
3 commits
9ceb71286c
...
f9b4470f92
Author | SHA1 | Date | |
---|---|---|---|
|
f9b4470f92 | ||
|
ec5d553e75 | ||
|
c92a9fb824 |
5 changed files with 471 additions and 1 deletions
1
.dockerignore
Normal file
1
.dockerignore
Normal file
|
@ -0,0 +1 @@
|
||||||
|
*
|
1
.env
Normal file
1
.env
Normal file
|
@ -0,0 +1 @@
|
||||||
|
HOME=/app
|
16
Dockerfile
16
Dockerfile
|
@ -1,5 +1,6 @@
|
||||||
# modified from https://hub.docker.com/r/cwaffles/openpose
|
# modified from https://hub.docker.com/r/cwaffles/openpose
|
||||||
FROM nvidia/cuda:11.4.0-cudnn8-devel-ubuntu20.04
|
FROM nvidia/cuda:11.4.0-cudnn8-devel-ubuntu20.04
|
||||||
|
#FROM nvidia/cuda:11.8.0-cudnn8-devel-ubuntu22.04
|
||||||
|
|
||||||
#get deps
|
#get deps
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
|
@ -32,10 +33,23 @@ RUN make install
|
||||||
RUN cp /openpose/build/python/openpose/pyopenpose.cpython-38-x86_64-linux-gnu.so /usr/local/lib/python3.8/dist-packages/
|
RUN cp /openpose/build/python/openpose/pyopenpose.cpython-38-x86_64-linux-gnu.so /usr/local/lib/python3.8/dist-packages/
|
||||||
WORKDIR /usr/local/lib/python3.8/dist-packages
|
WORKDIR /usr/local/lib/python3.8/dist-packages
|
||||||
RUN ln -s pyopenpose.cpython-38-x86_64-linux-gnu.so pyopenpose
|
RUN ln -s pyopenpose.cpython-38-x86_64-linux-gnu.so pyopenpose
|
||||||
|
#RUN cp /openpose/build/python/openpose/pyopenpose.cpython-310-x86_64-linux-gnu.so /usr/local/lib/python3.10/dist-packages/
|
||||||
|
#WORKDIR /usr/local/lib/python3.10/dist-packages
|
||||||
|
#RUN ln -s pyopenpose.cpython-310-x86_64-linux-gnu.so pyopenpose
|
||||||
#ENV LD_LIBRARY_PATH=/openpose/build/python/openpose
|
#ENV LD_LIBRARY_PATH=/openpose/build/python/openpose
|
||||||
|
|
||||||
RUN apt-get install -y ffmpeg
|
RUN apt-get install -y ffmpeg
|
||||||
|
|
||||||
WORKDIR /openpose
|
# Notebook to actually run code
|
||||||
|
#RUN apt-get install -y python3-notebook
|
||||||
|
RUN pip install ipywidgets --ignore-installed
|
||||||
|
RUN pip install notebook
|
||||||
|
RUN pip install ipywebrtc
|
||||||
|
EXPOSE 8888
|
||||||
|
|
||||||
|
# Possibly, use /home/user/app & set HOME to /home/user to avoid temporary directories in the app folder (if desired)
|
||||||
|
WORKDIR /app
|
||||||
|
ENV HOME /app
|
||||||
|
#CMD ["env"]
|
||||||
|
CMD ["jupyter", "notebook", "--ip", "0.0.0.0"]
|
||||||
|
|
||||||
|
|
433
app/openpose.ipynb
Normal file
433
app/openpose.ipynb
Normal file
|
@ -0,0 +1,433 @@
|
||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 1,
|
||||||
|
"id": "4268bbde",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"#hide\n",
|
||||||
|
"import pyopenpose as op\n",
|
||||||
|
"import ipywidgets\n",
|
||||||
|
"from ipywebrtc import CameraStream\n",
|
||||||
|
"import cv2 \n",
|
||||||
|
" "
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 2,
|
||||||
|
"id": "9b530e24",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"Defaulting to user installation because normal site-packages is not writeable\n",
|
||||||
|
"Requirement already satisfied: tqdm in ./.local/lib/python3.8/site-packages (4.64.1)\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"!pip3 install tqdm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 3,
|
||||||
|
"id": "47bbe21b",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
" from tqdm.notebook import tqdm\n",
|
||||||
|
"\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "8185becc",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"# Jupyter notebook for OpenPose experiments\n",
|
||||||
|
"\n",
|
||||||
|
"Read any image or video from the `/data` directory and render the output here "
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 4,
|
||||||
|
"id": "cb916658",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"Wed Nov 2 15:54:10 2022 \r\n",
|
||||||
|
"+-----------------------------------------------------------------------------+\r\n",
|
||||||
|
"| NVIDIA-SMI 470.141.03 Driver Version: 470.141.03 CUDA Version: 11.4 |\r\n",
|
||||||
|
"|-------------------------------+----------------------+----------------------+\r\n",
|
||||||
|
"| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |\r\n",
|
||||||
|
"| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |\r\n",
|
||||||
|
"| | | MIG M. |\r\n",
|
||||||
|
"|===============================+======================+======================|\r\n",
|
||||||
|
"| 0 NVIDIA GeForce ... On | 00000000:08:00.0 Off | N/A |\r\n",
|
||||||
|
"| 0% 41C P5 61W / 350W | 21MiB / 24265MiB | 0% Default |\r\n",
|
||||||
|
"| | | N/A |\r\n",
|
||||||
|
"+-------------------------------+----------------------+----------------------+\r\n",
|
||||||
|
" \r\n",
|
||||||
|
"+-----------------------------------------------------------------------------+\r\n",
|
||||||
|
"| Processes: |\r\n",
|
||||||
|
"| GPU GI CI PID Type Process name GPU Memory |\r\n",
|
||||||
|
"| ID ID Usage |\r\n",
|
||||||
|
"|=============================================================================|\r\n",
|
||||||
|
"+-----------------------------------------------------------------------------+\r\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"!nvidia-smi"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 5,
|
||||||
|
"id": "5222af69",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# CameraStream.facing_user(audio=False)\n",
|
||||||
|
"# requires SSL website... :-("
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 6,
|
||||||
|
"id": "266062c5",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"params = dict()\n",
|
||||||
|
"params[\"model_folder\"] = \"/openpose/models/\"\n",
|
||||||
|
"# params[\"face\"] = True\n",
|
||||||
|
"params[\"hand\"] = True\n",
|
||||||
|
"# params[\"heatmaps_add_parts\"] = True\n",
|
||||||
|
"# params[\"heatmaps_add_bkg\"] = True\n",
|
||||||
|
"# params[\"heatmaps_add_PAFs\"] = True\n",
|
||||||
|
"# params[\"heatmaps_scale\"] = 3\n",
|
||||||
|
"# params[\"upsampling_ratio\"] = 1\n",
|
||||||
|
"# params[\"body\"] = 1"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 7,
|
||||||
|
"id": "f034fee8",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"Starting OpenPose Python Wrapper...\n",
|
||||||
|
"Auto-detecting all available GPUs... Detected 1 GPU(s), using 1 of them starting at GPU 0.\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"# Starting OpenPose\n",
|
||||||
|
"opWrapper = op.WrapperPython()\n",
|
||||||
|
"opWrapper.configure(params)\n",
|
||||||
|
"opWrapper.start()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 8,
|
||||||
|
"id": "37f999b3",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# upload = ipywidgets.FileUpload()\n",
|
||||||
|
"# display(upload)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "de658948",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 9,
|
||||||
|
"id": "1b4208da",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"\n",
|
||||||
|
"# # Process Image\n",
|
||||||
|
"# datum = op.Datum()\n",
|
||||||
|
"# imageToProcess = cv2.imread(args[0].image_path)\n",
|
||||||
|
"# datum.cvInputData = imageToProcess\n",
|
||||||
|
"# opWrapper.emplaceAndPop(op.VectorDatum([datum]))\n",
|
||||||
|
"\n",
|
||||||
|
"# # Display Image\n",
|
||||||
|
"# print(\"Body keypoints: \\n\" + str(datum.poseKeypoints))\n",
|
||||||
|
"# print(\"Face keypoints: \\n\" + str(datum.faceKeypoints))\n",
|
||||||
|
"# print(\"Left hand keypoints: \\n\" + str(datum.handKeypoints[0]))\n",
|
||||||
|
"# print(\"Right hand keypoints: \\n\" + str(datum.handKeypoints[1]))\n",
|
||||||
|
"# cv2.imwrite(\"/data/result_body.jpg\",datum.cvOutputData)\n",
|
||||||
|
"\n",
|
||||||
|
"# print(dir(datum))\n",
|
||||||
|
"# # cv2.imshow(\"OpenPose 1.7.0 - Tutorial Python API\", datum.cvOutputData)\n",
|
||||||
|
"# cv2.waitKey(0)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "3c994aa0",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "402ad8b9",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 39,
|
||||||
|
"id": "275242c6",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"Frames per second : 24.0 FPS\n",
|
||||||
|
"Frame count : 500.0\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"vid_capture = cv2.VideoCapture('/data/0001-0500.mp4')\n",
|
||||||
|
"\n",
|
||||||
|
"# Create a video capture object, in this case we are reading the video from a file\n",
|
||||||
|
"if (vid_capture.isOpened() == False):\n",
|
||||||
|
" print(\"Error opening the video file\")\n",
|
||||||
|
"# Read fps and frame count\n",
|
||||||
|
"else:\n",
|
||||||
|
" # Get frame rate information\n",
|
||||||
|
" # You can replace 5 with CAP_PROP_FPS as well, they are enumerations\n",
|
||||||
|
" fps = vid_capture.get(5)\n",
|
||||||
|
" print('Frames per second : ', fps,'FPS')\n",
|
||||||
|
" \n",
|
||||||
|
" # Get frame count\n",
|
||||||
|
" # You can replace 7 with CAP_PROP_FRAME_COUNT as well, they are enumerations\n",
|
||||||
|
" frame_count = vid_capture.get(7)\n",
|
||||||
|
" print('Frame count : ', frame_count)\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 40,
|
||||||
|
"id": "b0149d40",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"frame_size = (int(vid_capture.get(cv2.CAP_PROP_FRAME_WIDTH)), int(vid_capture.get(cv2.CAP_PROP_FRAME_HEIGHT\n",
|
||||||
|
")))"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 41,
|
||||||
|
"id": "2a536a5f",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"(1920, 1080)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 41,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"frame_size"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 44,
|
||||||
|
"id": "66003b23",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"output = cv2.VideoWriter(\n",
|
||||||
|
" '/data/output.mp4',\n",
|
||||||
|
" # see http://mp4ra.org/#/codecs for codecs\n",
|
||||||
|
"# cv2.VideoWriter_fourcc('m','p','4','v'),\n",
|
||||||
|
"# cv2.VideoWriter_fourcc(*'mp4v'),\n",
|
||||||
|
"# cv2.VideoWriter_fourcc('a','v','1','C'),\n",
|
||||||
|
" cv2.VideoWriter_fourcc(*'vp09'),\n",
|
||||||
|
"# cv2.VideoWriter_fourcc(*'avc1'),\n",
|
||||||
|
" fps,\n",
|
||||||
|
" frame_size)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 45,
|
||||||
|
"id": "6ed91535",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"application/vnd.jupyter.widget-view+json": {
|
||||||
|
"model_id": "633b01b6f8474017ab2ba3d454b3adea",
|
||||||
|
"version_major": 2,
|
||||||
|
"version_minor": 0
|
||||||
|
},
|
||||||
|
"text/plain": [
|
||||||
|
" 0%| | 0/500.0 [00:00<?, ?it/s]"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "display_data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"Stream ended\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"# See also:\n",
|
||||||
|
"# with tqdm.wrapattr(file_obj, \"read\", total=file_obj.size) as fobj:\n",
|
||||||
|
"# ... while True:\n",
|
||||||
|
"# ... chunk = fobj.read(chunk_size)\n",
|
||||||
|
"# ... if not chunk:\n",
|
||||||
|
"# ... break\n",
|
||||||
|
"t = tqdm(total=frame_count) # Initialise\n",
|
||||||
|
"while(vid_capture.isOpened()):\n",
|
||||||
|
" t.update(1)\n",
|
||||||
|
"# with tqdm.wrapattr(vid_capture, \"read\", total=frame_count) as vid_obj:\n",
|
||||||
|
"# # vid_capture.read() methods returns a tuple, first element is a bool \n",
|
||||||
|
" # and the second is frame\n",
|
||||||
|
"# while True:\n",
|
||||||
|
" ret, frame = vid_capture.read()\n",
|
||||||
|
" if ret == True:\n",
|
||||||
|
" datum = op.Datum()\n",
|
||||||
|
" datum.cvInputData = frame\n",
|
||||||
|
" opWrapper.emplaceAndPop(op.VectorDatum([datum]))\n",
|
||||||
|
"\n",
|
||||||
|
" # Display Image\n",
|
||||||
|
" # print(\"Body keypoints: \\n\" + str(datum.poseKeypoints))\n",
|
||||||
|
" # print(\"Face keypoints: \\n\" + str(datum.faceKeypoints))\n",
|
||||||
|
" # print(\"Left hand keypoints: \\n\" + str(datum.handKeypoints[0]))\n",
|
||||||
|
" # print(\"Right hand keypoints: \\n\" + str(datum.handKeypoints[1]))\n",
|
||||||
|
" #cv2.imwrite(f\"/data/out/result_body_scale{i:03d}.jpg\",datum.cvOutputData)\n",
|
||||||
|
" output.write(datum.cvOutputData)\n",
|
||||||
|
" else:\n",
|
||||||
|
" print(\"Stream ended\")\n",
|
||||||
|
" break\n",
|
||||||
|
"t.close()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 46,
|
||||||
|
"id": "27580409",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# Release the objects\n",
|
||||||
|
"vid_capture.release()\n",
|
||||||
|
"output.release()"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 47,
|
||||||
|
"id": "6f3ad121",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"application/vnd.jupyter.widget-view+json": {
|
||||||
|
"model_id": "9b632f75a46f4306bcadddf38ac4887e",
|
||||||
|
"version_major": 2,
|
||||||
|
"version_minor": 0
|
||||||
|
},
|
||||||
|
"text/plain": [
|
||||||
|
"Video(value=b'\\x00\\x00\\x00\\x1cftypisom\\x00\\x00\\x02\\x00isomiso2mp41\\x00\\x00\\x00\\x08free\\x00U\\x0c\\xe0...')"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 47,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"# display(HTML(\"\"\"<video width=\"100\" height=\"100\" controls><source src=\"/data/output.mp4\" type=\"video/mp4\"></video>\"\"\"))\n",
|
||||||
|
"ipywidgets.Video.from_file('/data/outputs.mp4')"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "6d97230b",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"# from IPython.display import Video\n",
|
||||||
|
"# Video('/data/outputs.mp4', embed=True)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": null,
|
||||||
|
"id": "0b462645",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"kernelspec": {
|
||||||
|
"display_name": "Python 3 (ipykernel)",
|
||||||
|
"language": "python",
|
||||||
|
"name": "python3"
|
||||||
|
},
|
||||||
|
"language_info": {
|
||||||
|
"codemirror_mode": {
|
||||||
|
"name": "ipython",
|
||||||
|
"version": 3
|
||||||
|
},
|
||||||
|
"file_extension": ".py",
|
||||||
|
"mimetype": "text/x-python",
|
||||||
|
"name": "python",
|
||||||
|
"nbconvert_exporter": "python",
|
||||||
|
"pygments_lexer": "ipython3",
|
||||||
|
"version": "3.8.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 5
|
||||||
|
}
|
21
docker-compose.yml
Normal file
21
docker-compose.yml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
version: "3.3"
|
||||||
|
services:
|
||||||
|
suspicion_nb:
|
||||||
|
image: suspicion_nb
|
||||||
|
restart: always
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./Dockerfile
|
||||||
|
user: "1000:1000"
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
reservations:
|
||||||
|
devices:
|
||||||
|
- driver: nvidia
|
||||||
|
count: 1
|
||||||
|
capabilities: [gpu]
|
||||||
|
ports:
|
||||||
|
- "8888:8888"
|
||||||
|
volumes:
|
||||||
|
- ./data:/data
|
||||||
|
- ./app:/app
|
Loading…
Reference in a new issue