-
Notifications
You must be signed in to change notification settings - Fork 5.6k
[feature]: 🚀 Dockerfile for Railway #9718
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: preview
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| FROM node:22-alpine AS jsbuild | ||
| ENV PNPM_HOME=/pnpm | ||
| ENV PATH=/pnpm:/pnpm/bin:$PATH | ||
| ENV CI=1 | ||
| ENV TURBO_TELEMETRY_DISABLED=1 | ||
| RUN corepack enable | ||
| RUN apk add --no-cache libc6-compat | ||
| WORKDIR /app | ||
| COPY . . | ||
| RUN pnpm add -g turbo@2.9.18 | ||
| RUN pnpm install --frozen-lockfile | ||
| RUN turbo run build --filter=web --filter=admin --filter=live | ||
|
|
||
| FROM python:3.12.10-alpine AS apibuild | ||
| ENV PYTHONDONTWRITEBYTECODE=1 | ||
| ENV PYTHONUNBUFFERED=1 | ||
| ENV PIP_DISABLE_PIP_VERSION_CHECK=1 | ||
| WORKDIR /code | ||
| RUN apk add --no-cache libpq libxslt xmlsec ca-certificates openssl libffi-dev | ||
| COPY apps/api/requirements.txt ./requirements.txt | ||
| COPY apps/api/requirements ./requirements | ||
| RUN apk add --no-cache --virtual .build-deps \ | ||
| "bash~=5.2" g++ gcc cargo git make postgresql-dev libc-dev linux-headers \ | ||
| && pip install -r requirements.txt --compile --no-cache-dir \ | ||
| && apk del .build-deps \ | ||
| && rm -rf /var/cache/apk/* | ||
| COPY apps/api/manage.py manage.py | ||
| COPY apps/api/plane plane/ | ||
| COPY apps/api/templates templates/ | ||
| COPY apps/api/package.json package.json | ||
| COPY apps/api/bin ./bin/ | ||
| RUN apk add --no-cache "bash~=5.2" && mkdir -p /code/plane/logs && chmod +x ./bin/* | ||
|
|
||
| FROM caddy:2.11.3-alpine AS caddyimg | ||
|
|
||
| FROM python:3.12.10-alpine AS runner | ||
| WORKDIR /app | ||
| RUN apk add --no-cache libpq libxslt xmlsec ca-certificates openssl nss-tools bash curl | ||
|
|
||
| COPY --from=jsbuild /usr/lib /usr/lib | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- repository conventions ---'
find /tmp/coderabbit-repo-knowledge/makeplane-plane-9ea351f0 -type f -path '*/conventions/*' -o -path '*/learnings/*' -o -path '*/architecture/*' 2>/dev/null | sort | head -80
printf '%s\n' '--- Dockerfile ---'
cat -n Dockerfile
printf '%s\n' '--- supervisor configuration ---'
cat -n deploy/railway/supervisor.conf
printf '%s\n' '--- related runtime references ---'
rg -n --glob 'Dockerfile*' --glob '*.conf' --glob '*.yml' --glob '*.yaml' 'node:22|python:3\.12|COPY --from=jsbuild|supervisord|USER|caddy|uvicorn|gunicorn' . deploy 2>/dev/null | head -160Repository: makeplane/plane Length of output: 11794 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- applicable repository convention ---'
cat /tmp/coderabbit-repo-knowledge/makeplane-plane-9ea351f0/conventions/repo-wide.md
printf '%s\n' '--- comparable multi-stage Dockerfiles ---'
cat -n deployments/aio/community/Dockerfile | sed -n '1,65p'
printf '%s\n' '--- image tooling availability ---'
command -v docker || true
command -v skopeo || true
command -v crane || true
printf '%s\n' '--- Docker Hub tag metadata ---'
python3 - <<'PY'
import json, urllib.request
for repo, tag in [('library/node','22-alpine'), ('library/python','3.12.10-alpine'), ('library/caddy','2.11.3-alpine')]:
url = f'https://hub.docker.com/v2/repositories/{repo}/tags/{tag}'
try:
with urllib.request.urlopen(url, timeout=15) as r:
data = json.load(r)
print(repo, tag, json.dumps({
'digest': data.get('images', [{}])[0].get('digest'),
'architecture': data.get('images', [{}])[0].get('architecture'),
'os': data.get('images', [{}])[0].get('os'),
'last_updated': data.get('last_updated'),
}, sort_keys=True))
except Exception as e:
print(repo, tag, type(e).__name__, str(e))
PYRepository: makeplane/plane Length of output: 4422 🏁 Script executed: #!/bin/bash
set -eu
python3 - <<'PY'
import json, ssl, urllib.request
ctx = ssl._create_unverified_context()
for repo, tag in [('library/node','22-alpine'), ('library/python','3.12.10-alpine'), ('library/caddy','2.11.3-alpine')]:
url = f'https://hub.docker.com/v2/repositories/{repo}/tags/{tag}'
try:
req = urllib.request.Request(url, headers={'User-Agent': 'verification'})
with urllib.request.urlopen(req, context=ctx, timeout=15) as r:
data = json.load(r)
print(repo, tag)
print(json.dumps({
'digest': data.get('digest'),
'last_updated': data.get('last_updated'),
'images': [
{k: image.get(k) for k in ('architecture', 'os', 'digest', 'variant')}
for image in data.get('images', [])
],
}, sort_keys=True))
except Exception as e:
print(repo, tag, type(e).__name__, str(e))
PYRepository: makeplane/plane Length of output: 6503 🏁 Script executed: #!/bin/bash
set -eu
python3 - <<'PY'
import json, ssl, urllib.parse, urllib.request
ctx = ssl._create_unverified_context()
def get(url, headers=None):
req = urllib.request.Request(url, headers=headers or {'User-Agent': 'verification'})
with urllib.request.urlopen(req, context=ctx, timeout=30) as r:
return json.load(r)
for name, digest in [
('library/node', 'sha256:c610fcdfb1d5b4740dd70c284ed3cb16bb857e0f7166196e36a5501df7a3aa32'),
('library/python', 'sha256:4bbf5ef9ce4b273299d394de268ad6018e10a9375d7efc7c2ce9501a6eb6b86c'),
]:
token = get('https://auth.docker.io/token?' + urllib.parse.urlencode({
'service': 'registry.docker.io', 'scope': f'repository:{name}:pull'
}))['token']
headers = {
'Authorization': f'Bearer {token}',
'Accept': 'application/vnd.docker.distribution.manifest.list.v2+json,application/vnd.oci.image.index.v1+json,application/vnd.docker.distribution.manifest.v2+json,application/vnd.oci.image.manifest.v1+json',
'User-Agent': 'verification',
}
manifest = get(f'https://registry-1.docker.io/v2/{name}/manifests/{digest}', headers)
if 'manifests' in manifest:
chosen = next(x for x in manifest['manifests']
if x.get('platform', {}).get('architecture') == 'amd64'
and x.get('platform', {}).get('os') == 'linux')
manifest = get(f"https://registry-1.docker.io/v2/{name}/manifests/{chosen['digest']}", headers)
config_digest = manifest['config']['digest']
config = get(f'https://registry-1.docker.io/v2/{name}/blobs/{config_digest}', headers)
print(f'--- {name} ---')
print('config', config_digest)
print('rootfs diff_ids', len(config.get('rootfs', {}).get('diff_ids', [])))
for h in config.get('history', []):
created = h.get('created_by', '')
if 'alpine' in created.lower() or 'ADD' in created or 'FROM' in created:
print(created)
print('labels', config.get('config', {}).get('Labels'))
PYRepository: makeplane/plane Length of output: 3593 Use compatible base images before copying system libraries. 🤖 Prompt for AI Agents |
||
| COPY --from=jsbuild /usr/local/lib /usr/local/lib | ||
| COPY --from=jsbuild /usr/local/include /usr/local/include | ||
| COPY --from=jsbuild /usr/local/bin /usr/local/bin | ||
| COPY --from=apibuild /usr/local/lib/python3.12/site-packages/ /usr/local/lib/python3.12/site-packages/ | ||
| COPY --from=apibuild /usr/local/bin/ /usr/local/bin/ | ||
|
|
||
| COPY --from=caddyimg /usr/bin/caddy /usr/bin/caddy | ||
|
|
||
| COPY --from=apibuild /code /app/backend | ||
| COPY --from=jsbuild /app/apps/web/build/client /app/web | ||
| COPY --from=jsbuild /app/apps/admin/build/client /app/admin | ||
|
|
||
| COPY --from=jsbuild /app/packages /app/live/packages | ||
| COPY --from=jsbuild /app/node_modules /app/live/node_modules | ||
| COPY --from=jsbuild /app/apps/live/dist /app/live/apps/live/dist | ||
| COPY --from=jsbuild /app/apps/live/node_modules /app/live/apps/live/node_modules | ||
| COPY --from=jsbuild /app/apps/live/package.json /app/live/apps/live/package.json | ||
|
|
||
| RUN pip install supervisor --no-cache-dir | ||
| RUN mkdir -p /etc/supervisor/conf.d /app/logs /app/data /app/proxy | ||
|
|
||
| COPY deploy/railway/Caddyfile /app/proxy/Caddyfile | ||
| COPY deploy/railway/supervisor.conf /etc/supervisor/conf.d/supervisor.conf | ||
| COPY deploy/railway/start.sh /app/start.sh | ||
| RUN chmod +x /app/start.sh | ||
|
|
||
| CMD ["/app/start.sh"] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| { | ||
| auto_https off | ||
| servers { | ||
| max_header_size 25MB | ||
| client_ip_headers X-Forwarded-For X-Real-IP | ||
| trusted_proxies static 0.0.0.0/0 | ||
| } | ||
| } | ||
|
|
||
| :{$PORT} { | ||
| request_body { | ||
| max_size {$FILE_SIZE_LIMIT} | ||
| } | ||
|
|
||
| handle /live/* { | ||
| reverse_proxy localhost:3005 | ||
| } | ||
|
|
||
| handle /api/* { | ||
| reverse_proxy localhost:3004 | ||
| } | ||
|
|
||
| handle /auth/* { | ||
| reverse_proxy localhost:3004 | ||
| } | ||
|
|
||
| handle /static/* { | ||
| reverse_proxy localhost:3004 | ||
| } | ||
|
|
||
| handle_path /god-mode* { | ||
| root * /app/admin | ||
| try_files {path} {path}/ /index.html | ||
| file_server | ||
| } | ||
|
|
||
| handle_path /* { | ||
| root * /app/web | ||
| try_files {path} {path}/ /index.html | ||
| file_server | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| #!/bin/sh | ||
| export FILE_SIZE_LIMIT="${FILE_SIZE_LIMIT:-5242880}" | ||
| exec /usr/local/bin/supervisord -c /etc/supervisor/conf.d/supervisor.conf |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| [supervisord] | ||
| user=root | ||
| nodaemon=true | ||
| logfile=/app/logs/supervisord.log | ||
| pidfile=/tmp/supervisord.pid | ||
|
|
||
| [program:migrator] | ||
| directory=/app/backend | ||
| command=sh -c "./bin/docker-entrypoint-migrator.sh" | ||
| autostart=true | ||
| autorestart=unexpected | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- available repository guidance ---'
find /tmp/coderabbit-repo-knowledge/makeplane-plane-9ea351f0 -maxdepth 2 -type f -name '*.md' -print
printf '%s\n' '--- supervisor.conf ---'
cat -n deploy/railway/supervisor.conf
printf '%s\n' '--- related entrypoint references ---'
rg -n -C 4 'docker-entrypoint-migrator|startsecs|autorestart|exitcodes|priority' deploy Dockerfile* .github 2>/dev/null || trueRepository: makeplane/plane Length of output: 7130 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- applicable repository convention ---'
cat /tmp/coderabbit-repo-knowledge/makeplane-plane-9ea351f0/conventions/repo-wide.md
printf '%s\n' '--- migrator script locations ---'
fd -i 'docker-entrypoint-migrator\.sh$' .
printf '%s\n' '--- deployment files referencing the migrator ---'
rg -n -C 5 'docker-entrypoint-migrator|supervisord|supervisor\.conf' deploy Dockerfile* 2>/dev/null || trueRepository: makeplane/plane Length of output: 2952 🌐 Web query:
💡 Result: In Supervisor, the configuration parameters startsecs, autorestart, and exitcodes work together to manage process lifecycles as follows: startsecs The startsecs parameter defines the number of seconds a process must remain running after startup to be considered successfully started (moving from the STARTING to the RUNNING state) [1][2]. If a process exits before this duration, the start is considered a failure, even if the exit code is otherwise considered expected [1][2]. The default value is 1 [1][2]. Setting this to 0 indicates that the program does not need to stay running for any specific amount of time to be considered successful [1]. autorestart The autorestart parameter controls whether Supervisor should automatically restart a process if it exits while in the RUNNING state [3][4]. It accepts one of three values: - false: The process will never be automatically restarted [3][5]. - true: The process is unconditionally restarted when it exits, regardless of its exit code [3][5]. - unexpected (default): The process is restarted if it exits with an exit code that is not listed in the exitcodes configuration [3][5]. exitcodes The exitcodes parameter defines a list of expected exit codes used when autorestart is set to unexpected [3][4]. If the process exits with any code not present in this list, Supervisor treats the exit as unexpected and will restart the process [3][6]. The default value for exitcodes is 0 [3][4]. These settings specifically govern behavior once a process has reached the RUNNING state [2][3]. Behavior during the startup phase (STARTING state) is managed separately by startsecs and the startretries parameter [2][3][6]. Citations:
🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- migrator script ---'
cat -n apps/api/bin/docker-entrypoint-migrator.sh
printf '%s\n' '--- Dockerfile deployment context ---'
cat -n Dockerfile | sed -n '1,75p'
printf '%s\n' '--- migrator script metadata ---'
stat apps/api/bin/docker-entrypoint-migrator.shRepository: makeplane/plane Length of output: 3429 Set The migrator runs 🤖 Prompt for AI AgentsSource: MCP tools |
||
| stdout_logfile=/dev/fd/1 | ||
| stdout_logfile_maxbytes=0 | ||
| redirect_stderr=true | ||
| priority=10 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- repository guidance ---'
find /tmp/coderabbit-repo-knowledge/makeplane-plane-9ea351f0 -maxdepth 2 -type f -name '*.md' -print
for f in /tmp/coderabbit-repo-knowledge/makeplane-plane-9ea351f0/*/*.md; do
[ -f "$f" ] || continue
case "$f" in
*/learnings/*|*/architecture/*|*/conventions/*)
printf '\n--- %s ---\n' "$f"
sed -n '1,220p' "$f"
;;
esac
done
printf '%s\n' '--- target file ---'
cat -n deploy/railway/supervisor.conf
printf '%s\n' '--- nearby deployment files ---'
find deploy/railway -maxdepth 2 -type f -print 2>/dev/null | sort
printf '%s\n' '--- Dockerfiles ---'
find . -type f \( -iname 'Dockerfile' -o -iname 'Dockerfile.*' \) -print | sortRepository: makeplane/plane Length of output: 23447 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- migrator and supervisor launch paths ---'
rg -n --glob '!node_modules/**' --glob '!dist/**' 'docker-entrypoint-migrator\.sh|supervisord|supervisor\.conf|startsecs|priority=' .
printf '%s\n' '--- deploy/railway/start.sh ---'
cat -n deploy/railway/start.sh
printf '%s\n' '--- relevant Dockerfile sections ---'
for f in Dockerfile apps/api/Dockerfile.api; do
if [ -f "$f" ]; then
printf '\n--- %s ---\n' "$f"
rg -n -C 8 'supervisor|deploy/railway|docker-entrypoint-migrator|ENTRYPOINT|CMD' "$f"
fi
done
printf '%s\n' '--- migrator scripts ---'
find . -type f -name 'docker-entrypoint-migrator.sh' -print | while read -r f; do
printf '\n--- %s ---\n' "$f"
cat -n "$f"
doneRepository: makeplane/plane Length of output: 7859 🌐 Web query:
💡 Result: In Supervisor, process startup and shutdown order, as well as automatic restarts, are managed through specific configuration parameters. Priority and Start Order The priority parameter (default: 999) determines the relative order in which programs are started and shut down [1][2]. - Lower priorities: Programs start first and shut down last [1][3]. - Higher priorities: Programs start last and shut down first [1][3]. When using aggregate commands like start all or stop all, Supervisor uses these priority values to determine the sequence [1]. Note that Supervisor does not inherently wait for a process to reach the RUNNING state before starting the next one in the priority queue; it merely initiates them in that order [4][5]. To enforce strict dependency-based startup (e.g., waiting for a process to be fully RUNNING), external plugins or event listeners are often required [4][5]. Startsecs and Startup Success The startsecs parameter (default: 1) defines the time (in seconds) a process must remain running after startup to be considered successfully started [1][2]. - If a process exits before startsecs, it is considered a failed start, even if the exit code is otherwise considered expected [2]. - Once a process has stayed up for at least startsecs, it is considered to have transitioned from the STARTING state to the RUNNING state [1][6][2]. Autorestart Mechanism The autorestart parameter controls whether Supervisor should automatically restart a process if it exits [1][2]. - true: The process is unconditionally restarted upon exit [1][2]. - false: The process will not be automatically restarted [1][2]. - unexpected: The process is restarted only if it exits with an exit code not defined in the exitcodes configuration [1][2]. Autorestart only applies to processes that have successfully reached the RUNNING state [1][2]. If a process fails during the startup phase (before startsecs has elapsed), Supervisor handles retries based on the startretries parameter instead [1][2]. Process States Processes in Supervisor transition through various states, including STARTING, RUNNING, BACKOFF, EXITED, and FATAL [6]. - STARTING: The process is being started [6]. - RUNNING: The process has successfully started (ran longer than startsecs) [1][6]. - BACKOFF: The process transitioned to STARTING but exited too quickly (before startsecs) [6]. - FATAL: The process could not be started after the maximum number of startretries was exceeded [6]. An autorestarted process will not be restarted if it enters the FATAL state [6]. Citations:
Do not use
Run the migration before starting Supervisor, or add an explicit readiness gate. 🤖 Prompt for AI AgentsSource: MCP tools |
||
|
|
||
| [program:api] | ||
| directory=/app/backend | ||
| command=sh -c "./bin/docker-entrypoint-api.sh" | ||
| autostart=true | ||
| autorestart=true | ||
| environment=PORT=3004,HOSTNAME=0.0.0.0 | ||
| stdout_logfile=/dev/fd/1 | ||
| stdout_logfile_maxbytes=0 | ||
| redirect_stderr=true | ||
| priority=15 | ||
|
|
||
| [program:worker] | ||
| directory=/app/backend | ||
| command=sh -c "./bin/docker-entrypoint-worker.sh" | ||
| autostart=true | ||
| autorestart=true | ||
| stdout_logfile=/dev/fd/1 | ||
| stdout_logfile_maxbytes=0 | ||
| redirect_stderr=true | ||
| priority=20 | ||
|
|
||
| [program:beat] | ||
| directory=/app/backend | ||
| command=sh -c "./bin/docker-entrypoint-beat.sh" | ||
| autostart=true | ||
| autorestart=true | ||
| stdout_logfile=/dev/fd/1 | ||
| stdout_logfile_maxbytes=0 | ||
| redirect_stderr=true | ||
| priority=20 | ||
|
|
||
| [program:live] | ||
| command=sh -c "node /app/live/apps/live" | ||
| autostart=true | ||
| autorestart=true | ||
| environment=PORT=3005,HOSTNAME=0.0.0.0,API_BASE_URL=http://localhost:3004 | ||
| stdout_logfile=/dev/fd/1 | ||
| stdout_logfile_maxbytes=0 | ||
| redirect_stderr=true | ||
| priority=20 | ||
|
|
||
| [program:proxy] | ||
| directory=/app/proxy | ||
| command=sh -c "caddy run --config /app/proxy/Caddyfile" | ||
| autostart=true | ||
| autorestart=true | ||
| stdout_logfile=/dev/fd/1 | ||
| stdout_logfile_maxbytes=0 | ||
| redirect_stderr=true | ||
| priority=30 | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: makeplane/plane
Length of output: 6668
Security Misconfiguration (CWE-250)
Reachability: External · Exploitability: Difficult
Run application processes as a non-root user.
The final stage does not define
USER, and Supervisor configuresuser=root. If an exposed service is compromised, the attacker gains root privileges inside the container. Add a dedicated runtime user and configure Supervisor to use it.🤖 Prompt for AI Agents
Source: Linters/SAST tools