Initial commit: _deploy_app skill
Deploy new apps or push updates to existing deployments via Docker Compose + Caddy + Gitea webhooks. Multi-server profiles, auto-detection of deployment status, full infrastructure provisioning. - SKILL.md: 715-line workflow documentation - scripts/detect_deployment.py: deployment status detection - scripts/validate_compose.py: compose file validation - references/: infrastructure, compose patterns, Caddy patterns - assets/: Makefile and compose templates - config.json: mew server profile
This commit is contained in:
351
references/compose-patterns.md
Normal file
351
references/compose-patterns.md
Normal file
@@ -0,0 +1,351 @@
|
||||
# Docker Compose Patterns Reference
|
||||
|
||||
Reusable `docker-compose.yaml` templates for common application types deployed on mew. Every template includes the external `proxy` network required for Caddy reverse proxying.
|
||||
|
||||
---
|
||||
|
||||
## 1. Node.js / Express with Dockerfile Build
|
||||
|
||||
Build a Node.js app from a local Dockerfile. The container exposes an internal port that Caddy proxies to.
|
||||
|
||||
```yaml
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: myapp
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- "3000"
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- PORT=3000
|
||||
env_file:
|
||||
- .env
|
||||
networks:
|
||||
- proxy
|
||||
|
||||
networks:
|
||||
proxy:
|
||||
name: proxy
|
||||
external: true
|
||||
```
|
||||
|
||||
### Companion Dockerfile
|
||||
|
||||
```dockerfile
|
||||
FROM node:20-alpine
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm ci --omit=dev
|
||||
COPY . .
|
||||
EXPOSE 3000
|
||||
CMD ["node", "server.js"]
|
||||
```
|
||||
|
||||
### Notes
|
||||
|
||||
- Use `expose` (not `ports`) to keep the port internal to Docker networks only.
|
||||
- Set `container_name` to a unique, descriptive name — Caddy uses this name in its `reverse_proxy` directive.
|
||||
- The app listens on port 3000 inside the container. Caddy reaches it via `myapp:3000`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Python / FastAPI with Dockerfile Build
|
||||
|
||||
Build a Python FastAPI app from a local Dockerfile. Uses Uvicorn as the ASGI server.
|
||||
|
||||
```yaml
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: myapi
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- "8000"
|
||||
environment:
|
||||
- PYTHONUNBUFFERED=1
|
||||
env_file:
|
||||
- .env
|
||||
networks:
|
||||
- proxy
|
||||
|
||||
networks:
|
||||
proxy:
|
||||
name: proxy
|
||||
external: true
|
||||
```
|
||||
|
||||
### Companion Dockerfile
|
||||
|
||||
```dockerfile
|
||||
FROM python:3.12-slim
|
||||
WORKDIR /app
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
COPY . .
|
||||
EXPOSE 8000
|
||||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
```
|
||||
|
||||
### Notes
|
||||
|
||||
- `PYTHONUNBUFFERED=1` ensures log output appears immediately in `docker compose logs`.
|
||||
- For production, consider adding `--workers 4` to the Uvicorn command or switching to Gunicorn with Uvicorn workers.
|
||||
- Caddy reaches this via `myapi:8000`.
|
||||
|
||||
---
|
||||
|
||||
## 3. Static Site (nginx)
|
||||
|
||||
Serve pre-built static files (HTML, CSS, JS) via nginx.
|
||||
|
||||
```yaml
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: nginx:alpine
|
||||
container_name: mysite
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- "80"
|
||||
volumes:
|
||||
- ./dist:/usr/share/nginx/html:ro
|
||||
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
networks:
|
||||
- proxy
|
||||
|
||||
networks:
|
||||
proxy:
|
||||
name: proxy
|
||||
external: true
|
||||
```
|
||||
|
||||
### Companion nginx.conf
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
# Cache static assets
|
||||
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
|
||||
expires 30d;
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Notes
|
||||
|
||||
- Mount the build output directory (e.g., `./dist`) into the nginx html root.
|
||||
- The `try_files` fallback to `/index.html` supports client-side routing (React Router, Vue Router, etc.).
|
||||
- Mount the nginx config as read-only (`:ro`).
|
||||
- Caddy reaches this via `mysite:80`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Pre-built Image Only
|
||||
|
||||
Pull and run a published Docker image with no local build. Suitable for off-the-shelf applications like wikis, dashboards, and link pages.
|
||||
|
||||
```yaml
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: lscr.io/linuxserver/bookstack:latest
|
||||
container_name: bookstack
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- "6875"
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
- ./data:/config
|
||||
networks:
|
||||
- proxy
|
||||
|
||||
networks:
|
||||
proxy:
|
||||
name: proxy
|
||||
external: true
|
||||
```
|
||||
|
||||
### Notes
|
||||
|
||||
- Replace the `image` and `expose` port with whatever the application requires.
|
||||
- Check the image documentation for required environment variables and volume mount paths.
|
||||
- Persist application data by mounting a local `./data` directory.
|
||||
- Caddy reaches this via `bookstack:6875`.
|
||||
|
||||
---
|
||||
|
||||
## 5. App with PostgreSQL Database
|
||||
|
||||
A two-service stack with an application and a PostgreSQL database. The database is on an internal-only network. The app joins both the internal and proxy networks.
|
||||
|
||||
```yaml
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: myapp
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- "3000"
|
||||
env_file:
|
||||
- .env
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- proxy
|
||||
- internal
|
||||
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
container_name: myapp-db
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: ${POSTGRES_DB:-myapp}
|
||||
POSTGRES_USER: ${POSTGRES_USER:-myapp}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env}
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-myapp}"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- internal
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
|
||||
networks:
|
||||
proxy:
|
||||
name: proxy
|
||||
external: true
|
||||
internal:
|
||||
driver: bridge
|
||||
```
|
||||
|
||||
### Notes
|
||||
|
||||
- The database is **only** on the `internal` network — it is not reachable from Caddy or any other container outside this stack.
|
||||
- The app is on **both** `proxy` (so Caddy can reach it) and `internal` (so it can reach the database).
|
||||
- `depends_on` with `condition: service_healthy` ensures the app waits for PostgreSQL to be ready before starting.
|
||||
- The `${POSTGRES_PASSWORD:?...}` syntax causes compose to fail with an error if the variable is not set, preventing accidental deploys with no database password.
|
||||
- Use a named volume (`pgdata`) for database persistence.
|
||||
- In the app's `.env`, set the database URL:
|
||||
```
|
||||
DATABASE_URL=postgresql://myapp:secretpassword@myapp-db:5432/myapp
|
||||
```
|
||||
Note the hostname is the database container name (`myapp-db`), not `localhost`.
|
||||
|
||||
---
|
||||
|
||||
## 6. App with Environment File
|
||||
|
||||
Pattern for managing configuration through `.env` files with a `.env.example` template checked into version control.
|
||||
|
||||
```yaml
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: myapp
|
||||
restart: unless-stopped
|
||||
expose:
|
||||
- "3000"
|
||||
env_file:
|
||||
- .env
|
||||
networks:
|
||||
- proxy
|
||||
|
||||
networks:
|
||||
proxy:
|
||||
name: proxy
|
||||
external: true
|
||||
```
|
||||
|
||||
### Companion .env.example
|
||||
|
||||
Check this file into version control as a template. The actual `.env` file contains secrets and is listed in `.gitignore` on public repos only (on private Gitea repos, `.env` is committed per project conventions).
|
||||
|
||||
```env
|
||||
# Application
|
||||
NODE_ENV=production
|
||||
PORT=3000
|
||||
APP_URL=https://myapp.lavender-daydream.com
|
||||
|
||||
# Database (if applicable)
|
||||
DATABASE_URL=postgresql://user:password@myapp-db:5432/myapp
|
||||
|
||||
# Secrets
|
||||
SESSION_SECRET=generate-a-random-string-here
|
||||
API_KEY=your-api-key-here
|
||||
|
||||
# Email (Mailgun)
|
||||
MAILGUN_API_KEY=
|
||||
MAILGUN_DOMAIN=
|
||||
MAILGUN_FROM=noreply@lavender-daydream.com
|
||||
|
||||
# Deploy listener webhook secret (must match /etc/deploy-listener/deploy-listener.env)
|
||||
WEBHOOK_SECRET=must-match-deploy-listener
|
||||
```
|
||||
|
||||
### Notes
|
||||
|
||||
- The `env_file` directive in compose loads all variables from `.env` into the container environment.
|
||||
- Variables defined in `env_file` are available both to the containerized application and to compose variable interpolation (`${VAR}` syntax in the compose file).
|
||||
- Always provide a `.env.example` with placeholder values and comments explaining each variable.
|
||||
- For the deploy listener to work, the repo's webhook secret must match the value in `/etc/deploy-listener/deploy-listener.env`.
|
||||
|
||||
---
|
||||
|
||||
## Universal Compose Conventions
|
||||
|
||||
These conventions apply to ALL stacks on mew:
|
||||
|
||||
1. **Always include the proxy network** if Caddy needs to reach the container:
|
||||
```yaml
|
||||
networks:
|
||||
proxy:
|
||||
name: proxy
|
||||
external: true
|
||||
```
|
||||
|
||||
2. **Use `expose`, not `ports`**: Keep ports internal to Docker networks. Never bind to the host unless absolutely necessary.
|
||||
|
||||
3. **Set `container_name`** explicitly: Caddy resolves containers by name. Avoid auto-generated names.
|
||||
|
||||
4. **Set `restart: unless-stopped`**: Containers restart automatically after crashes or server reboots, but stay stopped if manually stopped.
|
||||
|
||||
5. **Use `env_file` for secrets**: Do not hardcode secrets in the compose file.
|
||||
|
||||
6. **Use health checks** for databases and critical dependencies to ensure proper startup ordering.
|
||||
|
||||
7. **Persist data with named volumes or bind mounts**: Never rely on container-internal storage for important data.
|
||||
Reference in New Issue
Block a user