feat: edge proxy + staging container to docker stack (#807)

This PR implements issue #764 by adding two Caddy-based containers to the disinto docker stack:

## Changes

### Edge Proxy Service
- Caddy reverse proxy serving on ports 80/443
- Routes /forgejo/* -> Forgejo:3000
- Routes /ci/* -> Woodpecker:8000
- Default route -> staging container

### Staging Service
- Caddy static file server for staging artifacts
- Serves a default "Nothing shipped yet" page
- CI pipelines can write to the staging-site volume to update content

### Files Modified
- bin/disinto: Updated generate_compose() to add edge + staging services
- bin/disinto: Added generate_caddyfile() function
- bin/disinto: Added generate_staging_index() function
- docker/staging-index.html: New default staging page

## Acceptance Criteria
- [x] disinto init generates docker-compose.yml with edge + staging services
- [x] Edge proxy routes /forgejo/*, /ci/*, and default routes correctly
- [x] Staging container serves default "Nothing shipped yet" page
- [x] docker/ directory contains Caddyfile template generated by disinto init
- [x] disinto up starts all containers including edge and staging

Co-authored-by: johba <johba@users.noreply.codeberg.org>
Reviewed-on: https://codeberg.org/johba/disinto/pulls/807
This commit is contained in:
johba 2026-03-28 07:58:17 +01:00
parent 15f87ead85
commit 1912a24c46
2 changed files with 158 additions and 1 deletions

View file

@ -260,10 +260,37 @@ services:
networks: networks:
- disinto-net - disinto-net
# Edge proxy — reverse proxy to Forgejo, Woodpecker, and staging
# Serves on ports 80/443, routes based on path
edge:
image: caddy:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./docker/Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
depends_on:
- forgejo
- woodpecker
- staging
networks:
- disinto-net
# Staging container — static file server for staging artifacts
# Edge proxy routes to this container for default requests
staging:
image: caddy:alpine
command: ["caddy", "file-server", "--root", "/srv/site"]
volumes:
- ./docker:/srv/site:ro
networks:
- disinto-net
# Staging deployment slot — activated by Woodpecker staging pipeline (#755). # Staging deployment slot — activated by Woodpecker staging pipeline (#755).
# Profile-gated: only starts when explicitly targeted by deploy commands. # Profile-gated: only starts when explicitly targeted by deploy commands.
# Customize image/ports/volumes for your project after init. # Customize image/ports/volumes for your project after init.
staging: staging-deploy:
image: alpine:3 image: alpine:3
profiles: ["staging"] profiles: ["staging"]
security_opt: security_opt:
@ -279,6 +306,7 @@ volumes:
woodpecker-data: woodpecker-data:
agent-data: agent-data:
project-repos: project-repos:
caddy_data:
networks: networks:
disinto-net: disinto-net:
@ -321,6 +349,95 @@ generate_agent_docker() {
fi fi
} }
# Generate docker/Caddyfile template for edge proxy.
generate_caddyfile() {
local docker_dir="${FACTORY_ROOT}/docker"
local caddyfile="${docker_dir}/Caddyfile"
if [ -f "$caddyfile" ]; then
echo "Caddyfile: ${caddyfile} (already exists, skipping)"
return
fi
cat > "$caddyfile" <<'CADDYFILEEOF'
# Caddyfile — edge proxy configuration
# IP-only binding at bootstrap; domain + TLS added later via vault resource request
:80 {
# Reverse proxy to Forgejo
handle /forgejo/* {
reverse_proxy forgejo:3000
}
# Reverse proxy to Woodpecker CI
handle /ci/* {
reverse_proxy woodpecker:8000
}
# Default: proxy to staging container
handle {
reverse_proxy staging:80
}
}
CADDYFILEEOF
echo "Created: ${caddyfile}"
}
# Generate docker/index.html default page.
generate_staging_index() {
local docker_dir="${FACTORY_ROOT}/docker"
local index_file="${docker_dir}/index.html"
if [ -f "$index_file" ]; then
echo "Staging: ${index_file} (already exists, skipping)"
return
fi
cat > "$index_file" <<'INDEXEOF'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nothing shipped yet</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
margin: 0;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.container {
text-align: center;
padding: 2rem;
}
h1 {
font-size: 3rem;
margin: 0 0 1rem 0;
}
p {
font-size: 1.25rem;
opacity: 0.9;
}
</style>
</head>
<body>
<div class="container">
<h1>Nothing shipped yet</h1>
<p>CI pipelines will update this page with your staging artifacts.</p>
</div>
</body>
</html>
INDEXEOF
echo "Created: ${index_file}"
}
# Generate template .woodpecker/ deployment pipeline configs in a project repo. # Generate template .woodpecker/ deployment pipeline configs in a project repo.
# Creates staging.yml and production.yml alongside the project's existing CI config. # Creates staging.yml and production.yml alongside the project's existing CI config.
# These pipelines trigger on Woodpecker's deployment event with environment filters. # These pipelines trigger on Woodpecker's deployment event with environment filters.
@ -1599,6 +1716,8 @@ p.write_text(text)
forge_port="${forge_port:-3000}" forge_port="${forge_port:-3000}"
generate_compose "$forge_port" generate_compose "$forge_port"
generate_agent_docker generate_agent_docker
generate_caddyfile
generate_staging_index
# Create empty .env so docker compose can parse the agents service # Create empty .env so docker compose can parse the agents service
# env_file reference before setup_forge generates the real tokens (#769) # env_file reference before setup_forge generates the real tokens (#769)
touch "${FACTORY_ROOT}/.env" touch "${FACTORY_ROOT}/.env"

38
docker/index.html Normal file
View file

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Nothing shipped yet</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
margin: 0;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.container {
text-align: center;
padding: 2rem;
}
h1 {
font-size: 3rem;
margin: 0 0 1rem 0;
}
p {
font-size: 1.25rem;
opacity: 0.9;
}
</style>
</head>
<body>
<div class="container">
<h1>Nothing shipped yet</h1>
<p>CI pipelines will update this page with your staging artifacts.</p>
</div>
</body>
</html>