前言
本文内的所有内容仅供学习。
本文先讲述后端和前端镜像的构建过程,然后以构建好的镜像进行搭建。
后端
项目地址
X-Tok-backend(后端):https://github.com/GIS142857/X-Tok-backend
Docker
X-Tok-backend未提供docker搭建方式,这里我们手动做一个Dockerfile。
检出仓库
使用 git clone 命令检出仓库。
添加install.sh
install.sh内容如下所示:
#!/bin/bash
mkdir -p /app
cd /app
mkdir -p storage storage/app storage/logs
rm public/storage && ln -s /app/douyin-backend/storage public/storage
go mod tidy && go mod vendor && CGO_ENABLED=0 go build cmd/web/main.godb_douyin.sql
仓库自带的数据库文件比较旧,可以通过向作者求取新的数据库文件。
添加entry.sh
entry.sh内容如下所示:
#!/bin/sh
# gorm_v2.yml
# MySQL
# - MYSQL_HOST
# - MYSQL_PORT
# - MYSQL_DATABSE
# - MYSQL_ROOT_PASSWORD
GORM_MYSQL_HOST=$(yq '.Gormv2.Mysql.Write.Host' /app/config/gorm_v2.yml)
GORM_MYSQL_PORT=$(yq '.Gormv2.Mysql.Write.Port' /app/config/gorm_v2.yml)
GORM_MYSQL_USER=$(yq '.Gormv2.Mysql.Write.User' /app/config/gorm_v2.yml)
GORM_MYSQL_DATABASE=$(yq '.Gormv2.Mysql.Write.DataBase' /app/config/gorm_v2.yml)
GORM_MYSQL_PASSWORD=$(yq '.Gormv2.Mysql.Write.Pass' /app/config/gorm_v2.yml)
if [ -n "${MYSQL_HOST}" ] && [ "${MYSQL_HOST}" != "${GORM_MYSQL_HOST}" ]; then
yq -i '.Gormv2.Mysql.Write.Host="'${MYSQL_HOST}'"' /app/config/gorm_v2.yml
yq -i '.Gormv2.Mysql.Read.Host="'${MYSQL_HOST}'"' /app/config/gorm_v2.yml
GORM_MYSQL_HOST=${MYSQL_HOST}
fi
if [ -n "${MYSQL_PORT}" ] && [ "${MYSQL_PORT}" != "${GORM_MYSQL_PORT}" ]; then
yq -i '.Gormv2.Mysql.Write.Port='${MYSQL_PORT}'' /app/config/gorm_v2.yml
yq -i '.Gormv2.Mysql.Read.Port='${MYSQL_PORT}'' /app/config/gorm_v2.yml
GORM_MYSQL_PORT=${MYSQL_PORT}
fi
if [ -n "${MYSQL_USER}" ] && [ "${MYSQL_USER}" != "${GORM_MYSQL_USER}" ]; then
yq -i '.Gormv2.Mysql.Write.User="'${MYSQL_USER}'"' /app/config/gorm_v2.yml
yq -i '.Gormv2.Mysql.Read.User="'${MYSQL_USER}'"' /app/config/gorm_v2.yml
GORM_MYSQL_USER=${MYSQL_USER}
fi
if [ -n "${MYSQL_DATABASE}" ] && [ "${MYSQL_DATABASE}" != "${GORM_MYSQL_DATABASE}" ]; then
yq -i '.Gormv2.Mysql.Write.DataBase="'${MYSQL_DATABASE}'"' /app/config/gorm_v2.yml
yq -i '.Gormv2.Mysql.Read.DataBase="'${MYSQL_DATABASE}'"' /app/config/gorm_v2.yml
GORM_MYSQL_DATABASE=${MYSQL_PASSWORD}
fi
if [ -n "${MYSQL_PASSWORD}" ] && [ "${MYSQL_PASSWORD}" != "${GORM_MYSQL_PASSWORD}" ]; then
yq -i '.Gormv2.Mysql.Write.Pass="'${MYSQL_PASSWORD}'"' /app/config/gorm_v2.yml
yq -i '.Gormv2.Mysql.Read.Pass="'${MYSQL_PASSWORD}'"' /app/config/gorm_v2.yml
GORM_MYSQL_PASSWORD=${MYSQL_PASSWORD}
fi
# config.yml
# HttpServer
# - WEB_PORT
# Redis
# - REDIS_HOST
# - REDIS_PORT
# - REDIS_AUTH
# FileUploadSetting
# - FILE_UPLOAD_SIZE
# - SOURCE_URL_PREFIX
# - UPLOAD_ROOT_PATH
CONFIG_WEB_PORT=$(yq '.HttpServer.Web.Port' /app/config/config.yml |awk -F ':' '{print $2}')
CONFIG_REDIS_HOST=$(yq '.Redis.Host' /app/config/config.yml)
CONFIG_REDIS_PORT=$(yq '.Redis.Port' /app/config/config.yml)
CONFIG_REDIS_AUTH=$(yq '.Redis.Auth' /app/config/config.yml)
CONFIG_FILE_UPLOAD_SIZE=$(yq '.FileUploadSetting.Size' /app/config/config.yml)
CONFIG_SOURCE_URL_PREFIX=$(yq '.FileUploadSetting.SourceUrlPrefix' /app/config/config.yml)
CONFIG_UPLOAD_ROOT_PATH=$(yq '.FileUploadSetting.UploadRootPath' /app/config/config.yml)
if [ -n "${WEB_PORT}" ] && [ "${WEB_PORT}" != "${CONFIG_WEB_PORT}" ]; then
yq -i '.HttpServer.Web.Port=":'${WEB_PORT}'"' /app/config/config.yml
fi
if [ -n "${REDIS_HOST}" ] && [ "${REDIS_HOST}" != "${CONFIG_REDIS_HOST}" ]; then
yq -i '.Redis.Host="'${REDIS_HOST}'"' /app/config/config.yml
fi
if [ -n "${REDIS_PORT}" ] && [ "${REDIS_PORT}" != "${CONFIG_REDIS_PORT}" ]; then
yq -i '.Redis.Port='${REDIS_PORT}'' /app/config/config.yml
fi
if [ -n "${REDIS_AUTH}" ] && [ "${REDIS_AUTH}" != "${CONFIG_REDIS_AUTH}" ]; then
yq -i '.Redis.Auth="'${REDIS_AUTH}'"' /app/config/config.yml
fi
if [ -n "${FILE_UPLOAD_SIZE}" ] && [ "${FILE_UPLOAD_SIZE}" != "${CONFIG_FILE_UPLOAD_SIZE}" ]; then
yq -i '.FileUploadSetting.Size='${FILE_UPLOAD_SIZE}'' /app/config/config.yml
fi
if [ -n "${SOURCE_URL_PREFIX}" ] && [ "${SOURCE_URL_PREFIX}" != "${CONFIG_SOURCE_URL_PREFIX}" ]; then
yq -i '.FileUploadSetting.SourceUrlPrefix="'${SOURCE_URL_PREFIX}'"' /app/config/config.yml
fi
if [ -n "${UPLOAD_ROOT_PATH}" ] && [ "${UPLOAD_ROOT_PATH}" != "${CONFIG_UPLOAD_ROOT_PATH}" ]; then
yq -i '.FileUploadSetting.UploadRootPath="'${UPLOAD_ROOT_PATH}'"' /app/config/config.yml
fi
sleep 5
is_mysql_running() {
mysql -h $GORM_MYSQL_HOST -P $GORM_MYSQL_PORT -u $GORM_MYSQL_USER -p$GORM_MYSQL_PASSWORD -e "select version();"
if [ $? -eq 0 ]; then
return 0
else
return 1
fi
}
is_mysql_running
while [ $? -ne 0 ]; do
echo "Waiting for MySQL start ..."
sleep 1
is_mysql_running
done
result=$(mysql -h $GORM_MYSQL_HOST -P $GORM_MYSQL_PORT -u $GORM_MYSQL_USER -p$GORM_MYSQL_PASSWORD -D$GORM_MYSQL_DATABASE -e "show tables like 'tb_accounts'")
if [ -z "$result" ]; then
mysql -h $GORM_MYSQL_HOST -P $GORM_MYSQL_PORT -u $GORM_MYSQL_USER -p$GORM_MYSQL_PASSWORD $GORM_MYSQL_DATABASE < /app/db_douyin.sql
fi
mysql -h $GORM_MYSQL_HOST -P $GORM_MYSQL_PORT -u $GORM_MYSQL_USER -p$GORM_MYSQL_PASSWORD -D$GORM_MYSQL_DATABASE -e "ALTER DATABASE ${GORM_MYSQL_DATABASE} DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
#while true
#do
# sleep 1
#done
./mainMySQL与Redis使用第三方镜像。
添加Dockerfile
Dockerfile内容如下所示:
FROM golang:latest AS builder
WORKDIR /app
COPY ./ ./
RUN mkdir -p storage storage/app storage/logs && rm public/storage && ln -s /app/storage public/storage
RUN go mod tidy && go mod vendor && CGO_ENABLED=0 go build cmd/web/main.go
FROM alpine:latest
WORKDIR /app
COPY --from=builder /app/main /app/entry.sh /app/db_douyin.sql /app
RUN mkdir -p /app/upload /app/config /app/storage /app/storage/app /app/storage/logs /app/public
COPY --from=builder /app/config /app/config
VOLUME ["/app/upload"]
RUN apk update && apk add mysql-client yq ffmpeg && chmod a+x /app/entry.sh
ENTRYPOINT ["/app/entry.sh"]文件结构
如图所示:

构建镜像
使用如下命令镜像构建:
docker build -t 镜像名:标签 .例如:
docker build -t x-tok-backend:1.0.0前端
项目地址
X-Tok(前端):https://github.com/GIS142857/X-Tok
Docker
这里我们需要稍微修改一下Dockerfile。
检出仓库
使用 git clone 命令检出仓库。
添加entry.sh
entry.sh内容如下所示:
#!/bin/sh
cd /app
#cp nginx-x-tok.conf /etc/nginx/http.d
# nginx
# BACKEND_SERVER_HOST
# BACKEND_SERVER_PORT
# SERVER_NAME
# CLIENT_MAX_BODY_SIZE
NGINX_BACKEND_SERVER_HOST=$(cat /etc/nginx/http.d/nginx-x-tok.conf |grep '后端服务地址' |awk -F ' ' '{print $2}' |awk -F ':' '{print $1}')
NGINX_BACKEND_SERVER_PORT=$(cat /etc/nginx/http.d/nginx-x-tok.conf |grep '后端服务地址' |awk -F ' ' '{print $2}' |awk -F ':' '{print $2}' |awk -F ';' '{print $1}')
NGINX_SERVER_NAME=$(cat /etc/nginx/http.d/nginx-x-tok.conf |grep 'server_name' |awk -F' ' '{print $2}' |awk -F';' '{print $1}')
NGINX_LISTEN_PORT=$(cat /etc/nginx/http.d/nginx-x-tok.conf |grep 'listen' |awk -F ' ' '{print $2}' |awk -F ';' '{print $1}')
NGINX_CLIENT_MAX_BODY_SIZE=$(cat /etc/nginx/nginx.conf |grep 'client_max_body_size' |awk -F ' ' '{print $2}' |awk -F 'm' '{print $1}')
#echo "$NGINX_BACKEND_SERVER_HOST, $BACKEND_SERVER_HOST"
#echo "$NGINX_BACKEND_SERVER_PORT, $BACKEND_SERVER_PORT"
#echo "$NGINX_SERVER_NAME, $SERVER_NAME"
#echo "$NGINX_LISTEN_PORT,$LISTEN_PORT"
#echo "NGINX_CLIENT_MAX_BODY_SIZE, $CLIENT_MAX_BODY_SIZE"
if [ -n "$BACKEND_SERVER_HOST" ] || [-n "$BACKEND_SERVER_PORT" ]; then
if [ "$BACKEND_SERVER_HOST" != "$NGINX_BACKEND_SERVER_HOST" ] || [ "$BACKEND_SERVER_PORT" != "$NGINX_BACKEND_SERVER_PORT" ]; then
sed -i "s|server $NGINX_BACKEND_SERVER_HOST:$NGINX_BACKEND_SERVER_PORT|server $BACKEND_SERVER_HOST:$BACKEND_SERVER_PORT|g" /etc/nginx/http.d/nginx-x-tok.conf
fi
fi
if [ -n "$SERVER_NAME" ] && [ "$SERVER_NAME" != "$NGINX_SERVER_NAME" ]; then
sed -i "s/server_name $NGINX_SERVER_NAME/server_name $SERVER_NAME/g" /etc/nginx/http.d/nginx-x-tok.conf
fi
if [ -n "$LISTEN_PORT" ] && [ "$LISTEN_PORT" != "$NGINX_LISTEN_PORT" ]; then
sed -i "s/listen $NGINX_LISTEN_PORT/listen $LISTEN_PORT/g" /etc/nginx/http.d/nginx-x-tok.conf
fi
if [ -n "$CLIENT_MAX_BODY_SIZE" ] && [ "$CLIENT_MAX_BODY_SIZE" != "$NGINX_CLIENT_MAX_BODY_SIZE" ]; then
sed -i "s/client_max_body_size ${NGINX_CLIENT_MAX_BODY_SIZE}m/client_max_body_size ${CLIENT_MAX_BODY_SIZE}m/g" /etc/nginx/http.d/nginx-x-tok.conf
sed -i "s/client_max_body_size ${NGINX_CLIENT_MAX_BODY_SIZE}m/client_max_body_size ${CLIENT_MAX_BODY_SIZE}m/g" /etc/nginx/nginx.conf
fi
rebuild=false
CONFIG_BASE_URL=$(cat /app/src/config/index.ts |grep 'baseUrl: ' |awk -F ": " '{print $2}' |awk -F "'" '{print $2}')
CONFIG_FILE_PREVIEW=$(cat /app/src/config/index.ts |grep 'filePreview: ' |awk -F ": " '{print $2}' |awk -F "'" '{print $2}' |awk -F "/s" '{print $1}')
CONFIG_UNI=$(cat /app/src/config/index.ts |grep 'UNI: ' |awk -F ": " '{print $2}' |awk -F "'" '{print $2}')
#echo "$CONFIG_BASE_URL, $BASE_URL"
#echo "$CONFIG_FILE_PREVIEW, $FILE_PREVIEW"
#echo "$CONFIG_UNI, $UNI"
if [ -n "$BASE_URL" ] && [ "$BASE_URL" != "$CONFIG_BASE_URL" ]; then
sed -i "s|baseUrl: '$CONFIG_BASE_URL'|baseUrl: '$BASE_URL'|g" /app/src/config/index.ts
rebuild=true
fi
if [ -n "$FILE_PREVIEW" ] && [ "$FILE_PREVIEW" != "$CONFIG_FILE_PREVIEW" ]; then
sed -i "s|filePreview: '$CONFIG_FILE_PREVIEW/static/uploads/'|filePreview: '$FILE_PREVIEW/static/uploads/'|g" /app/src/config/index.ts
rebuild=true
fi
if [ -n "$UNI" ] && [ "$UNI" != "$CONFIG_UNI" ]; then
sed -i "s|UNI: '$CONFIG_UNI'|UNI: '$UNI'|g" /app/src/config/index.ts
rebuild=true
fi
if $rebuild; then
npm run build
fi
nginx -g "daemon off;"
添加nginx-x-tok.conf
nginx-x-tok.conf文件内容如下:
upstream backend_server_list {
ip_hash;
server localhost:22001; # 后端服务地址(IP_Addr替换为自己的IP地址)
}
server {
listen 3000; # managed by Certbot
server_name localhost; # 修改为自己的域名
# Vue 项目根目录
root /app/dist;
index index.html;
charset utf-8;
# 静态资源缓存配置
#location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ {
# expires 30d;
#}
location /videos/source/ {
root /app; # 根目录
autoindex on; # 开启目录索引(可选)
autoindex_exact_size off; # 显示文件大小(可选)
autoindex_localtime on; # 显示本地时间(可选)
}
location /videos/cover/ {
root /app; # 根目录
autoindex on; # 开启目录索引(可选)
autoindex_exact_size off; # 显示文件大小(可选)
autoindex_localtime on; # 显示本地时间(可选)
}
location /images/avatar_small/ {
root /app; # 根目录
autoindex on; # 开启目录索引(可选)
autoindex_exact_size off; # 显示文件大小(可选)
autoindex_localtime on; # 显示本地时间(可选)
}
location /images/avatar_large/ {
root /app; # 根目录
autoindex on; # 开启目录索引(可选)
autoindex_exact_size off; # 显示文件大小(可选)
autoindex_localtime on; # 显示本地时间(可选)
}
location /images/cover/ {
root /app; # 根目录
autoindex on; # 开启目录索引(可选)
autoindex_exact_size off; # 显示文件大小(可选)
autoindex_localtime on; # 显示本地时间(可选)
}
location /images/white_cover/ {
root /app; # 根目录
autoindex on; # 开启目录索引(可选)
autoindex_exact_size off; # 显示文件大小(可选)
autoindex_localtime on; # 显示本地时间(可选)
}
location ~ .*\.(js|css)?$ {
expires 12h;
}
location ~ /\. {
deny all;
}
location / {
#auth_basic "Login";
#auth_basic_user_file /etc/nginx/conf.d/passwd;
try_files $uri $uri/ /index.html;
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS, PUT, DELETE";
add_header Access-Control-Allow-Headers "Content-Type, Authorization, Token";
add_header Access-Control-Allow-Credentials true;
if ($request_method = OPTIONS) {
add_header Content-Length 0;
add_header Content-Type text/plain;
return 204;
}
}
# API 代理配置 (合并 /message/, /base/, /user/)
location ~ ^/(message|base|user|post|shop|video|jwt|upload)/ {
client_max_body_size 1m; # 允许最大请求体为 1MB
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 跨域支持
if ($request_method = 'OPTIONS') {
add_header 'Content-Type' 'text/plain, charset=utf-8';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type,X-Requested-With';
add_header 'Access-Control-Allow-Methods' 'GET,POST,DELETE,PUT,OPTIONS';
add_header 'Access-Control-Allow-Origin' '*';
return 204;
}
# 代理到后端服务
proxy_pass http://backend_server_list;
}
}修改Dockerfile
修改后的Dockerfile,如下所示:
# syntax = docker/dockerfile:experimental
FROM node:alpine3.21
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN apk update && apk add nginx && npm i -g corepack@latest && corepack enable
WORKDIR /app
COPY ./ ./
# RUN两次方便观察install和build, 也可以用pnpm cache and locked
RUN pnpm install && npm run build && chmod +x /app/entry.sh && cp nginx-x-tok.conf /etc/nginx/http.d/
ENTRYPOINT ["/app/entry.sh"]
#FROM --platform=${BUILDPLATFORM:-linux/amd64,linux/arm64} ghcr.io/rookie-luochao/nginx-runner:latest
#COPY --from=builder /x-tok/dist /app
#COPY --from=builder /x-tok/nginx-x-tok.conf /etc/nginx/conf.d/conf-base.template文件结构
如图所示:

构建镜像
使用如下命令镜像构建:
docker build -t 镜像名:标签 .例如:
docker build -t x-tok:1.0.0Docker Compose
使用docker-compose.yml的方式启动镜像。
如下所示:
services:
x-tok:
image: x-tok:1.0.0
restart: always
container_name: x-tok
networks:
- ubuntu-network
ports:
- 3000:3000
environment:
- BACKEND_SERVER_HOST=x-tok-backend
- BACKEND_SERVER_PORT=22001
- SERVER_NAME=douyin.example.com
- LISTEN_PORT=3000
- CLIENT_MAX_BODY_SIZE=1024 #unit: MB
- BASE_URL=https://douyin.example.com
- FILE_PREVIEW=https://douyin.example.com
- UNI=https://douyin.example.com
x-tok-backend:
image: x-tok-backend:1.0.0
restart: always
container_name: x-tok-backend
networks:
- ubuntu-network
ports:
- 22001:22001
environment:
- MYSQL_HOST=x-tok-mysql
- MYSQL_PORT=3306
- MYSQL_USER=db_douyin
- MYSQL_DATABASE=db_douyin
- MYSQL_PASSWORD=db_douyin
- REDIS_HOST=x-tok-redis
- REDIS_PORT=6379
- REDIS_AUTH=db_douyin
- FILE_UPLOAD_SIZE=1024 # unit: MB
- SOURCE_URL_PREFIX=https://douyin.example.com
- UPLOAD_ROOT_PATH=/app/upload
x-tok-mysql:
image: mariadb:latest
restart: always
container_name: x-tok-mysql
environment:
- MYSQL_ROOT_PASSWORD=db_douyin
- MYSQL_PASSWORD=db_douyin
- MYSQL_DATABASE=db_douyin
- MYSQL_USER=db_douyin
networks:
- ubuntu-network
volumes:
- ./mysql/data:/var/lib/mysql
expose:
- 3306
x-tok-redis:
image: redis:latest
restart: always
container_name: x-tok-redis
command: redis-server --requirepass 'db_douyin'
volumes:
- ./redis/data:/data
networks:
- ubuntu-network
expose:
- 6379
networks:
ubuntu-network:
name: ubuntu-network
external: true