后端 (Laravel 10 + Sanctum): - RBAC 四层权限系统 (users→roles→permissions→menus) - 门店隔离中间件 (BelongsToStore Trait + StoreIsolation Middleware) - 操作日志中间件 (自动记录写操作) - 权限检查中间件 (CheckPermission) - 16张数据库表迁移 (系统基础+RBAC+字典+配置) - 11个 Eloquent Model - Auth API (登录/登出/用户信息) - 系统设置模块 CRUD (门店/部门/职务/用户/角色/菜单/权限/字典/日志) - 45条 RESTful API 路由 - InitSeeder 初始数据 (超管/角色/76权限/31菜单) 前端 (Vue 3 + Element Plus + Vite): - Axios 请求封装 + Token 注入 - Pinia 状态管理 (user + permission store) - 动态路由 (服务端菜单→前端路由自动生成) - 后台布局 (侧边栏+顶栏+主内容区) - 登录页 + 仪表盘首页 - 系统设置 7 个 CRUD 页面 技术方案文档 (7卷): - 技术总览/数据库设计/API规范/RBAC设计/模块详设/小程序设计/部署方案
693 lines
15 KiB
Markdown
693 lines
15 KiB
Markdown
# 宫中有喜 — 部署方案
|
||
|
||
## 一、部署架构
|
||
|
||
```
|
||
┌─────────────────────────────────────────┐
|
||
│ CDN (可选) │
|
||
│ 静态资源加速 │
|
||
└──────────────────┬──────────────────────┘
|
||
│
|
||
┌──────────────────▼──────────────────────┐
|
||
│ Nginx (反向代理) │
|
||
│ 80/443 → 静态资源 / API / WebSocket │
|
||
└────┬──────────────┬─────────────────────┘
|
||
│ │
|
||
▼ ▼
|
||
┌─────────┐ ┌──────────────┐
|
||
│ Vue SPA │ │ PHP-FPM │
|
||
│ (静态) │ │ Laravel App │
|
||
└─────────┘ └──────┬───────┘
|
||
│
|
||
┌───────────┼───────────┐
|
||
▼ ▼ ▼
|
||
┌──────────┐ ┌────────┐ ┌─────────┐
|
||
│ MySQL 8 │ │ Redis │ │ Storage │
|
||
│ │ │ │ │ (文件) │
|
||
└──────────┘ └────────┘ └─────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 二、环境要求
|
||
|
||
| 组件 | 最低版本 | 推荐 |
|
||
|------|----------|------|
|
||
| OS | Ubuntu 20.04 / CentOS 8 | Ubuntu 22.04 |
|
||
| PHP | 8.1 | 8.2 |
|
||
| MySQL | 8.0 | 8.0 |
|
||
| Redis | 6.0 | 7.0 |
|
||
| Nginx | 1.18 | 1.24 |
|
||
| Node.js | 16 | 18 LTS |
|
||
| Composer | 2.x | 2.x |
|
||
| Docker | 20.10 | 24.x |
|
||
| Docker Compose | 2.x | 2.x |
|
||
|
||
### PHP 扩展
|
||
|
||
```
|
||
php-fpm php-mysql php-redis php-mbstring php-xml
|
||
php-curl php-gd php-zip php-bcmath php-intl
|
||
php-opcache php-fileinfo
|
||
```
|
||
|
||
---
|
||
|
||
## 三、Docker Compose 部署
|
||
|
||
### 3.1 docker-compose.yml
|
||
|
||
```yaml
|
||
version: '3.8'
|
||
|
||
services:
|
||
# Nginx 反向代理
|
||
nginx:
|
||
image: nginx:1.24-alpine
|
||
ports:
|
||
- "80:80"
|
||
- "443:443"
|
||
volumes:
|
||
- ./docker/nginx/conf.d:/etc/nginx/conf.d
|
||
- ./docker/nginx/ssl:/etc/nginx/ssl
|
||
- ./frontend/dist:/var/www/frontend
|
||
- ./backend/public:/var/www/backend/public
|
||
- ./backend/storage/app/public:/var/www/backend/storage/app/public
|
||
depends_on:
|
||
- php
|
||
networks:
|
||
- app-network
|
||
restart: unless-stopped
|
||
|
||
# PHP-FPM
|
||
php:
|
||
build:
|
||
context: ./docker/php
|
||
dockerfile: Dockerfile
|
||
volumes:
|
||
- ./backend:/var/www/backend
|
||
depends_on:
|
||
- mysql
|
||
- redis
|
||
networks:
|
||
- app-network
|
||
restart: unless-stopped
|
||
|
||
# Laravel 队列 Worker
|
||
queue:
|
||
build:
|
||
context: ./docker/php
|
||
dockerfile: Dockerfile
|
||
command: php artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
|
||
volumes:
|
||
- ./backend:/var/www/backend
|
||
depends_on:
|
||
- mysql
|
||
- redis
|
||
networks:
|
||
- app-network
|
||
restart: unless-stopped
|
||
|
||
# Laravel 定时任务
|
||
scheduler:
|
||
build:
|
||
context: ./docker/php
|
||
dockerfile: Dockerfile
|
||
command: /bin/sh -c "while true; do php /var/www/backend/artisan schedule:run --verbose --no-interaction >> /dev/null 2>&1; sleep 60; done"
|
||
volumes:
|
||
- ./backend:/var/www/backend
|
||
depends_on:
|
||
- mysql
|
||
- redis
|
||
networks:
|
||
- app-network
|
||
restart: unless-stopped
|
||
|
||
# MySQL
|
||
mysql:
|
||
image: mysql:8.0
|
||
ports:
|
||
- "3306:3306"
|
||
environment:
|
||
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
|
||
MYSQL_DATABASE: ${DB_DATABASE}
|
||
MYSQL_USER: ${DB_USERNAME}
|
||
MYSQL_PASSWORD: ${DB_PASSWORD}
|
||
volumes:
|
||
- mysql-data:/var/lib/mysql
|
||
- ./docker/mysql/my.cnf:/etc/mysql/conf.d/my.cnf
|
||
- ./docker/mysql/init:/docker-entrypoint-initdb.d
|
||
networks:
|
||
- app-network
|
||
restart: unless-stopped
|
||
|
||
# Redis
|
||
redis:
|
||
image: redis:7-alpine
|
||
ports:
|
||
- "6379:6379"
|
||
command: redis-server --requirepass ${REDIS_PASSWORD}
|
||
volumes:
|
||
- redis-data:/data
|
||
networks:
|
||
- app-network
|
||
restart: unless-stopped
|
||
|
||
volumes:
|
||
mysql-data:
|
||
redis-data:
|
||
|
||
networks:
|
||
app-network:
|
||
driver: bridge
|
||
```
|
||
|
||
### 3.2 PHP Dockerfile
|
||
|
||
```dockerfile
|
||
# docker/php/Dockerfile
|
||
FROM php:8.2-fpm-alpine
|
||
|
||
# 安装扩展
|
||
RUN apk add --no-cache \
|
||
libpng-dev libjpeg-turbo-dev freetype-dev \
|
||
libzip-dev icu-dev oniguruma-dev \
|
||
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
|
||
&& docker-php-ext-install \
|
||
pdo_mysql mbstring xml curl gd zip bcmath intl opcache pcntl \
|
||
&& pecl install redis \
|
||
&& docker-php-ext-enable redis
|
||
|
||
# Composer
|
||
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
|
||
|
||
# PHP 配置
|
||
COPY php.ini /usr/local/etc/php/conf.d/custom.ini
|
||
|
||
WORKDIR /var/www/backend
|
||
|
||
# 权限
|
||
RUN chown -R www-data:www-data /var/www/backend
|
||
|
||
USER www-data
|
||
|
||
EXPOSE 9000
|
||
CMD ["php-fpm"]
|
||
```
|
||
|
||
### 3.3 PHP 配置
|
||
|
||
```ini
|
||
; docker/php/php.ini
|
||
upload_max_filesize = 20M
|
||
post_max_size = 25M
|
||
memory_limit = 256M
|
||
max_execution_time = 60
|
||
date.timezone = Asia/Shanghai
|
||
|
||
; OPcache
|
||
opcache.enable=1
|
||
opcache.memory_consumption=128
|
||
opcache.interned_strings_buffer=8
|
||
opcache.max_accelerated_files=10000
|
||
opcache.validate_timestamps=0
|
||
opcache.revalidate_freq=0
|
||
```
|
||
|
||
### 3.4 Nginx 配置
|
||
|
||
```nginx
|
||
# docker/nginx/conf.d/default.conf
|
||
|
||
# 后台管理 SPA
|
||
server {
|
||
listen 80;
|
||
server_name admin.example.com;
|
||
|
||
root /var/www/frontend;
|
||
index index.html;
|
||
|
||
# SPA 路由
|
||
location / {
|
||
try_files $uri $uri/ /index.html;
|
||
}
|
||
|
||
# 静态资源缓存
|
||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2)$ {
|
||
expires 30d;
|
||
add_header Cache-Control "public, immutable";
|
||
}
|
||
|
||
# Gzip
|
||
gzip on;
|
||
gzip_types text/plain text/css application/json application/javascript text/xml;
|
||
gzip_min_length 1024;
|
||
}
|
||
|
||
# API 服务
|
||
server {
|
||
listen 80;
|
||
server_name api.example.com;
|
||
|
||
root /var/www/backend/public;
|
||
index index.php;
|
||
|
||
# Laravel 路由
|
||
location / {
|
||
try_files $uri $uri/ /index.php?$query_string;
|
||
}
|
||
|
||
# PHP-FPM
|
||
location ~ \.php$ {
|
||
fastcgi_pass php:9000;
|
||
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
|
||
include fastcgi_params;
|
||
fastcgi_read_timeout 60;
|
||
}
|
||
|
||
# 文件上传大小
|
||
client_max_body_size 25m;
|
||
|
||
# 静态资源
|
||
location /storage {
|
||
alias /var/www/backend/storage/app/public;
|
||
expires 30d;
|
||
}
|
||
|
||
# 禁止访问隐藏文件
|
||
location ~ /\. {
|
||
deny all;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3.5 MySQL 配置
|
||
|
||
```ini
|
||
# docker/mysql/my.cnf
|
||
[mysqld]
|
||
character-set-server = utf8mb4
|
||
collation-server = utf8mb4_unicode_ci
|
||
default-time-zone = '+08:00'
|
||
|
||
# InnoDB
|
||
innodb_buffer_pool_size = 512M
|
||
innodb_log_file_size = 128M
|
||
innodb_flush_log_at_trx_commit = 2
|
||
|
||
# 连接
|
||
max_connections = 200
|
||
wait_timeout = 600
|
||
|
||
# 慢查询
|
||
slow_query_log = 1
|
||
slow_query_log_file = /var/log/mysql/slow.log
|
||
long_query_time = 2
|
||
|
||
# 二进制日志(用于备份恢复)
|
||
log-bin = mysql-bin
|
||
binlog_expire_logs_seconds = 604800
|
||
binlog_format = ROW
|
||
|
||
[client]
|
||
default-character-set = utf8mb4
|
||
```
|
||
|
||
---
|
||
|
||
## 四、环境变量 (.env)
|
||
|
||
```bash
|
||
# .env (生产环境示例)
|
||
APP_NAME="宫中有喜"
|
||
APP_ENV=production
|
||
APP_KEY=base64:xxxx
|
||
APP_DEBUG=false
|
||
APP_URL=https://api.example.com
|
||
|
||
# 数据库
|
||
DB_CONNECTION=mysql
|
||
DB_HOST=mysql
|
||
DB_PORT=3306
|
||
DB_DATABASE=gongzhongyouxi
|
||
DB_USERNAME=appuser
|
||
DB_PASSWORD=strong_password_here
|
||
DB_ROOT_PASSWORD=root_strong_password
|
||
|
||
# Redis
|
||
REDIS_HOST=redis
|
||
REDIS_PASSWORD=redis_password
|
||
REDIS_PORT=6379
|
||
|
||
# 缓存/队列/会话
|
||
CACHE_DRIVER=redis
|
||
QUEUE_CONNECTION=redis
|
||
SESSION_DRIVER=redis
|
||
|
||
# 文件存储
|
||
FILESYSTEM_DISK=local
|
||
# 或 OSS:
|
||
# FILESYSTEM_DISK=oss
|
||
# OSS_ACCESS_KEY_ID=xxx
|
||
# OSS_ACCESS_KEY_SECRET=xxx
|
||
# OSS_BUCKET=xxx
|
||
# OSS_ENDPOINT=xxx
|
||
|
||
# 微信小程序
|
||
WECHAT_MINI_APP_ID=wxXXXXXXXXXXX
|
||
WECHAT_MINI_SECRET=xxxxxxxxxx
|
||
|
||
# 微信支付
|
||
WECHAT_PAY_MCH_ID=xxxxxxxxxx
|
||
WECHAT_PAY_KEY=xxxxxxxxxx
|
||
WECHAT_PAY_CERT_PATH=/path/to/cert
|
||
WECHAT_PAY_KEY_PATH=/path/to/key
|
||
WECHAT_PAY_NOTIFY_URL=https://api.example.com/api/v1/client/pay/callback
|
||
|
||
# 短信
|
||
SMS_DRIVER=aliyun
|
||
SMS_KEY=xxx
|
||
SMS_SECRET=xxx
|
||
SMS_SIGN=宫中有喜
|
||
|
||
# 日志
|
||
LOG_CHANNEL=daily
|
||
LOG_LEVEL=warning
|
||
```
|
||
|
||
---
|
||
|
||
## 五、部署流程
|
||
|
||
### 5.1 首次部署
|
||
|
||
```bash
|
||
# 1. 克隆代码
|
||
git clone <repo> /opt/saas
|
||
cd /opt/saas
|
||
|
||
# 2. 配置环境变量
|
||
cp .env.example .env
|
||
vim .env # 填入生产配置
|
||
|
||
# 3. 构建前端
|
||
cd frontend
|
||
npm install
|
||
npm run build
|
||
cd ..
|
||
|
||
# 4. 启动容器
|
||
docker compose up -d
|
||
|
||
# 5. 初始化 Laravel
|
||
docker compose exec php composer install --no-dev --optimize-autoloader
|
||
docker compose exec php php artisan key:generate
|
||
docker compose exec php php artisan migrate --force
|
||
docker compose exec php php artisan db:seed --class=InitSeeder
|
||
docker compose exec php php artisan storage:link
|
||
docker compose exec php php artisan config:cache
|
||
docker compose exec php php artisan route:cache
|
||
docker compose exec php php artisan view:cache
|
||
|
||
# 6. 验证
|
||
curl https://api.example.com/api/v1/health
|
||
```
|
||
|
||
### 5.2 更新部署
|
||
|
||
```bash
|
||
# 1. 拉取代码
|
||
cd /opt/saas
|
||
git pull origin main
|
||
|
||
# 2. 更新后端依赖
|
||
docker compose exec php composer install --no-dev --optimize-autoloader
|
||
|
||
# 3. 数据库迁移
|
||
docker compose exec php php artisan migrate --force
|
||
|
||
# 4. 清除缓存
|
||
docker compose exec php php artisan config:cache
|
||
docker compose exec php php artisan route:cache
|
||
docker compose exec php php artisan view:cache
|
||
docker compose exec php php artisan queue:restart
|
||
|
||
# 5. 更新前端(如有变更)
|
||
cd frontend
|
||
npm install
|
||
npm run build
|
||
cd ..
|
||
|
||
# 6. 重载 Nginx
|
||
docker compose exec nginx nginx -s reload
|
||
```
|
||
|
||
### 5.3 零停机部署(可选)
|
||
|
||
```bash
|
||
# 使用 Laravel 维护模式
|
||
docker compose exec php php artisan down --retry=60
|
||
|
||
# 执行部署步骤...
|
||
|
||
docker compose exec php php artisan up
|
||
```
|
||
|
||
---
|
||
|
||
## 六、备份策略
|
||
|
||
### 6.1 数据库备份
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
# scripts/backup-db.sh
|
||
BACKUP_DIR="/opt/backups/mysql"
|
||
DATE=$(date +%Y%m%d_%H%M)
|
||
KEEP_DAYS=30
|
||
|
||
mkdir -p $BACKUP_DIR
|
||
|
||
# 备份
|
||
docker compose exec -T mysql mysqldump \
|
||
-u root -p"${DB_ROOT_PASSWORD}" \
|
||
--single-transaction \
|
||
--routines --triggers \
|
||
${DB_DATABASE} | gzip > "${BACKUP_DIR}/db_${DATE}.sql.gz"
|
||
|
||
# 清理旧备份
|
||
find $BACKUP_DIR -name "*.sql.gz" -mtime +$KEEP_DAYS -delete
|
||
|
||
echo "[$(date)] Backup completed: db_${DATE}.sql.gz"
|
||
```
|
||
|
||
### 6.2 文件备份
|
||
|
||
```bash
|
||
#!/bin/bash
|
||
# scripts/backup-files.sh
|
||
BACKUP_DIR="/opt/backups/files"
|
||
DATE=$(date +%Y%m%d)
|
||
|
||
mkdir -p $BACKUP_DIR
|
||
|
||
# 备份上传文件
|
||
tar -czf "${BACKUP_DIR}/storage_${DATE}.tar.gz" \
|
||
-C /opt/saas/backend storage/app/public
|
||
|
||
# 清理 30 天前
|
||
find $BACKUP_DIR -name "*.tar.gz" -mtime +30 -delete
|
||
```
|
||
|
||
### 6.3 定时任务
|
||
|
||
```bash
|
||
# crontab -e
|
||
# 每天凌晨 3 点备份数据库
|
||
0 3 * * * /opt/saas/scripts/backup-db.sh >> /var/log/backup.log 2>&1
|
||
# 每周日凌晨 4 点备份文件
|
||
0 4 * * 0 /opt/saas/scripts/backup-files.sh >> /var/log/backup.log 2>&1
|
||
```
|
||
|
||
---
|
||
|
||
## 七、监控与日志
|
||
|
||
### 7.1 健康检查
|
||
|
||
```php
|
||
// routes/api.php
|
||
Route::get('/health', function () {
|
||
try {
|
||
DB::connection()->getPdo();
|
||
Redis::ping();
|
||
return response()->json([
|
||
'status' => 'healthy',
|
||
'timestamp' => now()->toISOString(),
|
||
'services' => [
|
||
'database' => 'ok',
|
||
'redis' => 'ok',
|
||
'storage' => is_writable(storage_path()) ? 'ok' : 'error',
|
||
]
|
||
]);
|
||
} catch (\Exception $e) {
|
||
return response()->json(['status' => 'unhealthy', 'error' => $e->getMessage()], 500);
|
||
}
|
||
});
|
||
```
|
||
|
||
### 7.2 日志管理
|
||
|
||
```php
|
||
// config/logging.php — 生产配置
|
||
'channels' => [
|
||
'daily' => [
|
||
'driver' => 'daily',
|
||
'path' => storage_path('logs/laravel.log'),
|
||
'level' => 'warning',
|
||
'days' => 14,
|
||
],
|
||
'business' => [
|
||
'driver' => 'daily',
|
||
'path' => storage_path('logs/business.log'),
|
||
'level' => 'info',
|
||
'days' => 30,
|
||
],
|
||
],
|
||
```
|
||
|
||
### 7.3 监控检查清单
|
||
|
||
| 监控项 | 方法 | 告警条件 |
|
||
|--------|------|----------|
|
||
| 服务存活 | /health 接口 | 连续 3 次失败 |
|
||
| CPU 使用率 | 系统监控 | > 80% 持续 5 分钟 |
|
||
| 内存使用率 | 系统监控 | > 85% |
|
||
| 磁盘使用率 | 系统监控 | > 80% |
|
||
| MySQL 连接数 | SHOW STATUS | > 150 |
|
||
| MySQL 慢查询 | 慢查询日志 | > 10 条/小时 |
|
||
| Redis 内存 | INFO memory | > 80% maxmemory |
|
||
| 队列积压 | queue:monitor | > 100 条待处理 |
|
||
| Laravel 日志 | 日志文件 | ERROR 级别 |
|
||
| 响应时间 | Nginx access_log | P95 > 2s |
|
||
| SSL 证书 | 定期检查 | 到期前 30 天 |
|
||
|
||
---
|
||
|
||
## 八、安全加固
|
||
|
||
### 8.1 服务器
|
||
|
||
```bash
|
||
# 防火墙 — 仅开放必要端口
|
||
ufw allow 22/tcp # SSH
|
||
ufw allow 80/tcp # HTTP
|
||
ufw allow 443/tcp # HTTPS
|
||
ufw enable
|
||
|
||
# SSH 加固
|
||
# /etc/ssh/sshd_config
|
||
PermitRootLogin no
|
||
PasswordAuthentication no
|
||
PubkeyAuthentication yes
|
||
```
|
||
|
||
### 8.2 Docker 安全
|
||
|
||
- 不使用 root 运行容器(PHP Dockerfile 已配置 www-data)
|
||
- MySQL/Redis 端口不暴露到公网(仅限 Docker 内部网络)
|
||
- 定期更新基础镜像
|
||
- 使用 `.dockerignore` 排除敏感文件
|
||
|
||
### 8.3 应用安全
|
||
|
||
| 措施 | 配置 |
|
||
|------|------|
|
||
| HTTPS 强制 | Nginx redirect 80→443 |
|
||
| CORS | 限定域名白名单 |
|
||
| CSRF | Sanctum SPA 模式自动处理 |
|
||
| Rate Limiting | Laravel throttle 中间件 |
|
||
| SQL 注入 | Eloquent 参数绑定 |
|
||
| XSS | HTMLPurifier + CSP Header |
|
||
| 文件上传 | 白名单 + 大小限制 + 病毒扫描 |
|
||
| 敏感数据 | .env 不入 Git,密钥加密存储 |
|
||
|
||
### 8.4 SSL 证书
|
||
|
||
```bash
|
||
# Let's Encrypt 免费证书
|
||
apt install certbot python3-certbot-nginx
|
||
certbot --nginx -d admin.example.com -d api.example.com
|
||
|
||
# 自动续期
|
||
certbot renew --dry-run
|
||
# crontab
|
||
0 0 1 */2 * certbot renew --quiet
|
||
```
|
||
|
||
---
|
||
|
||
## 九、性能优化
|
||
|
||
### 9.1 Laravel 优化
|
||
|
||
```bash
|
||
# 配置/路由/视图缓存
|
||
php artisan config:cache
|
||
php artisan route:cache
|
||
php artisan view:cache
|
||
|
||
# Composer 自动加载优化
|
||
composer install --optimize-autoloader --no-dev
|
||
```
|
||
|
||
### 9.2 MySQL 优化
|
||
|
||
- 索引覆盖常用查询(见 02-database-design.md 索引策略)
|
||
- 开启查询缓存(Redis 层面)
|
||
- 大表分页使用 cursor 分页
|
||
- 定期 ANALYZE TABLE
|
||
|
||
### 9.3 Redis 缓存策略
|
||
|
||
| 缓存键 | TTL | 场景 |
|
||
|--------|-----|------|
|
||
| user:{id} | 30min | 用户信息 |
|
||
| menu:{role_id} | 60min | 角色菜单 |
|
||
| room:board:{store_id}:{date} | 5min | 房态看板 |
|
||
| config:{key} | 60min | 系统配置 |
|
||
| dict:{type} | 24h | 数据字典 |
|
||
|
||
### 9.4 前端优化
|
||
|
||
- Vite 构建:代码分割 + Tree Shaking
|
||
- 路由懒加载:按模块动态 import
|
||
- 图片压缩:WebP 格式 + CDN
|
||
- Gzip:Nginx 开启 gzip 压缩
|
||
|
||
---
|
||
|
||
## 十、目录结构(Docker 相关)
|
||
|
||
```
|
||
saas/
|
||
├── docker/
|
||
│ ├── nginx/
|
||
│ │ ├── conf.d/
|
||
│ │ │ └── default.conf
|
||
│ │ └── ssl/ # SSL 证书
|
||
│ ├── php/
|
||
│ │ ├── Dockerfile
|
||
│ │ └── php.ini
|
||
│ └── mysql/
|
||
│ ├── my.cnf
|
||
│ └── init/ # 初始化 SQL
|
||
├── scripts/
|
||
│ ├── backup-db.sh
|
||
│ ├── backup-files.sh
|
||
│ └── deploy.sh
|
||
├── docker-compose.yml
|
||
├── .env
|
||
└── .env.example
|
||
```
|