mirror of
https://github.com/Evil0ctal/Douyin_TikTok_Download_API.git
synced 2026-10-28 12:31:24 +08:00
**Publishing.** `docker-publish.yml` builds and pushes on a branch push, a `v*` tag, or by hand, for linux/amd64 and linux/arm64. Two images, and deliberately not three: the app (api, worker and migrate are one image differing only in the verb) and the Go downloader sidecar. browser-rpc is not published — it installs CloakBrowser from a pinned commit passed in as a build argument, so an image built here would ship somebody else's browser under this project's name and make the pin ours rather than the operator's. It stays a local build. `latest` moves only on a semver tag. A branch push gets a branch tag and an immutable sha tag, so pulling `latest` gets a release somebody decided to make rather than whatever was merged an hour ago. A fork without credentials builds without pushing instead of failing, and a manual run defaults to build-only so the workflow can be exercised without publishing. The two slow parts no longer run under emulation. The console stage emits JavaScript and CSS, which are not architecture-specific, so it is pinned to BUILDPLATFORM and built once; the downloader cross-compiles with GOARCH and runs its tests natively, because a cross-compiled test binary cannot be executed at the point it is built. Verified: a two-platform downloader build produces a manifest carrying both linux/amd64 and linux/arm64. The smoke test is what the image is actually for. `--version` would have tested the usage message and nothing else - the entrypoint takes a ROLE, not a flag - so it runs `dtk --version` through a shell, checks the console assets reached the runtime stage (a separate build stage that can silently produce nothing), and checks the entrypoint still refuses an unknown role. **README.** Adds what v4's has and v5's did not: a capability matrix per platform (checked against the endpoint tables, including that Douyin's follower and following lists are absent because Douyin serves them only to a signed-in session), the link formats accepted, the stack, contact details, star history, and CI and Docker Hub badges. The Chinese README's sponsor block was English prose. It is Chinese now; the English one stays English. Both files now carry the same twelve sections. `DTK_IMAGE` and `DTK_DOWNLOADER_IMAGE` make the image name overridable, because the README was about to document a variable compose did not read. The default stays a local name: `up` on a clean machine should produce something runnable without depending on a registry. The `pull` step is spelled out, since these services declare both `image` and `build` and compose builds rather than pulls when the image is absent. CI now also watches `main`, since v5 is what `main` will hold and a workflow that only watches the old branch name goes quiet exactly when the rename matters.
134 lines
5.3 KiB
Docker
134 lines
5.3 KiB
Docker
# syntax=docker/dockerfile:1
|
|
#
|
|
# The api and worker images are the same image. They differ only in the verb
|
|
# handed to the entrypoint, because two images built from one source tree drift
|
|
# apart the first time somebody rebuilds one of them (docs/design/09-deployment.md).
|
|
#
|
|
# Three stages:
|
|
# console builds the React console with npm, so node never reaches runtime
|
|
# deps resolves Python dependencies with uv into a self-contained venv
|
|
# runtime carries the venv, the installed package and the built console
|
|
#
|
|
# The runtime image holds no compiler, no package manager, no default password
|
|
# and no writable application directory. It runs as an unprivileged user and is
|
|
# expected to be started with a read-only root filesystem plus a tmpfs on /tmp
|
|
# (docs/design/08-security.md).
|
|
|
|
ARG PYTHON_VERSION=3.12.8
|
|
ARG NODE_VERSION=22
|
|
# uv is pinned: the lockfile it resolves is the reproducibility guarantee, and a
|
|
# floating installer would quietly undo that.
|
|
ARG UV_VERSION=0.9.18
|
|
ARG PYTHON_BASE=python:${PYTHON_VERSION}-slim-bookworm
|
|
|
|
|
|
# --------------------------------------------------------------------------
|
|
# Stage 1: the console
|
|
# --------------------------------------------------------------------------
|
|
# `--platform=$BUILDPLATFORM`: this stage emits JavaScript, CSS and HTML, none of
|
|
# which is architecture-specific, so it is built once on the runner's own
|
|
# architecture and its output copied into every target image. Without it a
|
|
# two-platform build runs `npm ci` and vite twice, once under QEMU emulation,
|
|
# which is by far the slowest thing in the whole build for no gain at all.
|
|
FROM --platform=$BUILDPLATFORM node:${NODE_VERSION}-alpine AS console
|
|
|
|
WORKDIR /build
|
|
|
|
# Manifest and lockfile first: this layer is reused across every source-only
|
|
# change, and `npm ci` refuses to run without the lockfile, which is what keeps
|
|
# the console build reproducible.
|
|
COPY web/package.json web/package-lock.json ./
|
|
RUN npm ci --no-audit --no-fund
|
|
|
|
COPY web/ ./
|
|
|
|
# The output directory is a contract with the runtime stage. `test -d` turns a
|
|
# vite config that writes somewhere else into a build failure instead of an
|
|
# image that serves nothing.
|
|
ARG CONSOLE_OUT_DIR=dist
|
|
RUN npm run build && test -d "${CONSOLE_OUT_DIR}" && mv "${CONSOLE_OUT_DIR}" /console
|
|
|
|
|
|
# --------------------------------------------------------------------------
|
|
# Stage 2: Python dependencies
|
|
# --------------------------------------------------------------------------
|
|
FROM ghcr.io/astral-sh/uv:${UV_VERSION} AS uv
|
|
|
|
FROM ${PYTHON_BASE} AS deps
|
|
|
|
COPY --from=uv /uv /usr/local/bin/uv
|
|
|
|
ENV UV_LINK_MODE=copy \
|
|
UV_COMPILE_BYTECODE=1 \
|
|
UV_PYTHON_DOWNLOADS=never \
|
|
UV_PROJECT_ENVIRONMENT=/app/.venv
|
|
|
|
WORKDIR /app
|
|
|
|
# Dependencies before source: editing a Python file must not re-resolve or
|
|
# re-download the dependency set.
|
|
COPY pyproject.toml uv.lock ./
|
|
RUN --mount=type=cache,target=/root/.cache/uv \
|
|
uv sync --frozen --no-dev --no-install-project
|
|
|
|
COPY src ./src
|
|
# --no-editable installs a real copy into site-packages, so the runtime image
|
|
# does not need /app/src on disk and nothing in the image points at a build path.
|
|
RUN --mount=type=cache,target=/root/.cache/uv \
|
|
uv sync --frozen --no-dev --no-editable
|
|
|
|
|
|
# --------------------------------------------------------------------------
|
|
# Stage 3: runtime
|
|
# --------------------------------------------------------------------------
|
|
FROM ${PYTHON_BASE} AS runtime
|
|
|
|
ARG DTK_UID=10001
|
|
ARG DTK_GID=10001
|
|
|
|
RUN groupadd --system --gid "${DTK_GID}" dtk \
|
|
&& useradd --system --uid "${DTK_UID}" --gid "${DTK_GID}" \
|
|
--home-dir /app --shell /usr/sbin/nologin dtk
|
|
|
|
# HOME and the cache directory point at /tmp because the root filesystem is
|
|
# mounted read-only in production; /tmp is the one tmpfs the containers get.
|
|
ENV PYTHONUNBUFFERED=1 \
|
|
PYTHONDONTWRITEBYTECODE=1 \
|
|
VIRTUAL_ENV=/app/.venv \
|
|
PATH=/app/.venv/bin:$PATH \
|
|
HOME=/tmp \
|
|
XDG_CACHE_HOME=/tmp/.cache \
|
|
DTK_CONSOLE_DIR=/app/web/dist
|
|
|
|
WORKDIR /app
|
|
|
|
# Everything stays root-owned and read-only for the runtime user: the process
|
|
# has no business rewriting its own code, and the read-only rootfs then costs
|
|
# nothing.
|
|
COPY --from=deps --chown=root:root /app/.venv /app/.venv
|
|
COPY --from=console --chown=root:root /console /app/web/dist
|
|
COPY --chown=root:root --chmod=0555 docker/entrypoint.sh /usr/local/bin/dtk-entrypoint
|
|
|
|
# Backups are the one thing the runtime writes. The directory has to exist in
|
|
# the image and be owned by the runtime user: Docker seeds a named volume from
|
|
# the image path it covers, so an absent or root-owned path yields a root-owned
|
|
# volume that a non-root process cannot write - which is a permission error at
|
|
# the moment someone asks for a backup, not at startup.
|
|
RUN install -d -o dtk -g dtk -m 0750 /var/lib/dtk/backups
|
|
ENV DTK_BACKUP_DIR=/var/lib/dtk/backups
|
|
|
|
USER dtk
|
|
|
|
EXPOSE 8000
|
|
|
|
# No HEALTHCHECK here on purpose: the worker has no HTTP port, so a probe baked
|
|
# into the shared image would mark half the containers unhealthy. Compose gives
|
|
# each role its own check.
|
|
ENTRYPOINT ["dtk-entrypoint"]
|
|
CMD ["api"]
|
|
|
|
LABEL org.opencontainers.image.title="dtk" \
|
|
org.opencontainers.image.description="Self-hosted Douyin and TikTok data API" \
|
|
org.opencontainers.image.licenses="Apache-2.0" \
|
|
org.opencontainers.image.source="https://github.com/Evil0ctal/Douyin_TikTok_Download_API"
|