Skip to content

Commit bcba481

Browse files
committed
wip
1 parent a82705d commit bcba481

9 files changed

Lines changed: 275 additions & 82 deletions

File tree

content/docs/deploying.md

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,10 @@ If your app needs runtime variables:
4040

4141
Then restart or redeploy the pod to apply changes.
4242

43-
## 4. Add a Domain
43+
## 4. Add a Domain (Optional)
4444

45-
Your pod needs a domain to be accessible. You have two options:
45+
Add a domain only if this pod should be reachable publicly (from browser/users).
46+
You have two options:
4647

4748
- **Auto-generated** - Instant subdomain like `pod-abc123.1.2.3.4.sslip.io`
4849
- **Custom** - Your own domain like `myapp.example.com` (requires [DNS setup](/docs/domains))
@@ -58,7 +59,29 @@ Hit "Deploy" and watch the build logs. The process:
5859
3. Start the container
5960
4. Route traffic via Traefik
6061

61-
Once complete, your app is live at the domain URL.
62+
Once complete:
63+
- with domain: your app is live at the domain URL
64+
- without domain: your app is internal-only (pod-to-pod on the project network)
65+
66+
## 6. Internal Pod-to-Pod Communication
67+
68+
Pods can communicate internally without public domains.
69+
70+
- Internal hostnames are created automatically on deploy/restart
71+
- Current format: `<pod-slug>-<podID8>` (example: `api-gateway-9b77346e`)
72+
- A secondary readable alias is also added: `<pod-slug>-<project-slug>`
73+
74+
Use internal calls like:
75+
76+
```bash
77+
http://api-gateway-9b77346e:8080
78+
```
79+
80+
Important:
81+
- Hostname and port are separate. You must call `host:port`.
82+
- Browser clients cannot resolve internal Docker DNS names directly.
83+
- Internal DNS names are for server-side/container-side calls only.
84+
- Existing running containers get the new aliases after deploy/restart.
6285

6386
## Managing Your Pod
6487

content/docs/domains.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,31 @@ After setting the server domain:
3939

4040
## Pod Domains
4141

42-
Each pod needs at least one domain to be accessible.
42+
Pod domains are optional.
43+
44+
- If a pod should be public, add at least one domain.
45+
- If a pod is internal-only (pod-to-pod), you can run it without any domain.
4346

4447
Domain changes are not applied to a running container automatically.
4548
After adding, editing, or deleting pod domains, run **Deploy** or **Restart** to apply routing changes.
4649

50+
### Internal-Only Pods
51+
52+
Internal-only pods communicate over the project Docker network using internal DNS aliases.
53+
54+
- Primary alias format: `<pod-slug>-<podID8>`
55+
- Secondary alias format: `<pod-slug>-<project-slug>`
56+
57+
Example:
58+
59+
```bash
60+
http://api-gateway-9b77346e:8080
61+
```
62+
63+
Note:
64+
- Internal aliases are intended for container/server-side communication.
65+
- Browser clients cannot resolve these internal DNS names directly.
66+
4767
### Auto-Generated Domains
4868

4969
The quickest way to get started. Deeploy generates a subdomain using [sslip.io](https://sslip.io):

internal/server/app/app.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ func New(cfg *config.Config) (*App, error) {
6262
podEnvVarService := service.NewPodEnvVarService(podEnvVarRepo, encryptor)
6363
podDomainService := service.NewPodDomainService(podDomainRepo)
6464
gitTokenService := service.NewGitTokenService(gitTokenRepo, encryptor)
65-
deployService := service.NewDeployService(podRepo, podDomainRepo, podEnvVarService, gitTokenService, dockerService)
65+
deployService := service.NewDeployService(podRepo, projectRepo, podDomainRepo, podEnvVarService, gitTokenService, dockerService)
6666
traefikService := service.NewTraefikService(serverSettingsRepo, cfg.TraefikConfigDir, cfg.IsDevelopment())
6767

6868
return &App{

internal/server/docker/docker.go

Lines changed: 79 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -194,10 +194,6 @@ func (d *DockerService) RunContainer(ctx context.Context, opts RunContainerOptio
194194
//─────────────────────────────────────────────────────────────────────────
195195

196196
labels := map[string]string{
197-
// Enable Traefik for this container
198-
// Without this, Traefik ignores the container completely
199-
"traefik.enable": "true",
200-
201197
// Deeploy metadata for container identification
202198
"deeploy.pod.id": opts.PodID,
203199
}
@@ -209,44 +205,49 @@ func (d *DockerService) RunContainer(ctx context.Context, opts RunContainerOptio
209205
if d.isDevelopment {
210206
entrypoint = "web"
211207
}
208+
port := 8080
209+
if len(opts.Domains) > 0 {
210+
port = opts.Domains[0].Port
211+
}
212+
213+
if opts.AttachEdge {
214+
// Enable Traefik only for public pods.
215+
labels["traefik.enable"] = "true"
212216

213-
// Create a router for each domain
214-
// Each domain gets its own router but shares the same service (load balancer)
215-
for i, domain := range opts.Domains {
216-
routerName := fmt.Sprintf("%s-%d", opts.PodID, i)
217+
// Create a router for each domain
218+
// Each domain gets its own router but shares the same service (load balancer)
219+
for i, domain := range opts.Domains {
220+
routerName := fmt.Sprintf("%s-%d", opts.PodID, i)
217221

218-
// Routing rule: Which domain goes to this container?
219-
// Host(`example.com`) matches requests with that exact Host header
220-
labels["traefik.http.routers."+routerName+".rule"] = fmt.Sprintf("Host(`%s`)", domain.Domain)
222+
// Routing rule: Which domain goes to this container?
223+
// Host(`example.com`) matches requests with that exact Host header
224+
labels["traefik.http.routers."+routerName+".rule"] = fmt.Sprintf("Host(`%s`)", domain.Domain)
221225

222-
// Service: Where to forward the traffic
223-
// @docker suffix is required because Traefik auto-appends it to Docker services
224-
labels["traefik.http.routers."+routerName+".service"] = opts.PodID + "@docker"
226+
// Service: Where to forward the traffic
227+
// @docker suffix is required because Traefik auto-appends it to Docker services
228+
labels["traefik.http.routers."+routerName+".service"] = opts.PodID + "@docker"
225229

226-
// Entrypoint: Which port to listen on (web=80, websecure=443)
227-
labels["traefik.http.routers."+routerName+".entrypoints"] = entrypoint
230+
// Entrypoint: Which port to listen on (web=80, websecure=443)
231+
labels["traefik.http.routers."+routerName+".entrypoints"] = entrypoint
228232

229-
// SSL/TLS: Only in production (not development)
230-
// certresolver=letsencrypt tells Traefik to automatically get a certificate
231-
// from Let's Encrypt using the HTTP challenge
232-
if !d.isDevelopment {
233-
labels["traefik.http.routers."+routerName+".tls.certresolver"] = "letsencrypt"
233+
// SSL/TLS: Only in production (not development)
234+
// certresolver=letsencrypt tells Traefik to automatically get a certificate
235+
// from Let's Encrypt using the HTTP challenge
236+
if !d.isDevelopment {
237+
labels["traefik.http.routers."+routerName+".tls.certresolver"] = "letsencrypt"
238+
}
234239
}
235-
}
236240

237-
// Service configuration: One service for all routers
238-
// All domains for this pod route to the same container/port
239-
port := 8080
240-
if len(opts.Domains) > 0 {
241-
port = opts.Domains[0].Port
242-
}
243-
labels["traefik.http.services."+opts.PodID+".loadbalancer.server.port"] = fmt.Sprintf("%d", port)
241+
// Service configuration: One service for all routers
242+
// All domains for this pod route to the same container/port
243+
labels["traefik.http.services."+opts.PodID+".loadbalancer.server.port"] = fmt.Sprintf("%d", port)
244244

245-
// Health checks: Traefik pings each container every 2 seconds
246-
// Only containers that respond get traffic. This ensures zero-downtime
247-
// during redeploys - new container only gets traffic once it's ready.
248-
labels["traefik.http.services."+opts.PodID+".loadbalancer.healthcheck.path"] = "/"
249-
labels["traefik.http.services."+opts.PodID+".loadbalancer.healthcheck.interval"] = "2s"
245+
// Health checks: Traefik pings each container every 2 seconds
246+
// Only containers that respond get traffic. This ensures zero-downtime
247+
// during redeploys - new container only gets traffic once it's ready.
248+
labels["traefik.http.services."+opts.PodID+".loadbalancer.healthcheck.path"] = "/"
249+
labels["traefik.http.services."+opts.PodID+".loadbalancer.healthcheck.interval"] = "2s"
250+
}
250251

251252
// Container config
252253
config := &container.Config{
@@ -264,12 +265,25 @@ func (d *DockerService) RunContainer(ctx context.Context, opts RunContainerOptio
264265
RestartPolicy: container.RestartPolicy{Name: "unless-stopped"},
265266
}
266267

267-
// Network config - join the deeploy network so Traefik can reach this container
268-
networkConfig := &network.NetworkingConfig{
269-
EndpointsConfig: map[string]*network.EndpointSettings{
270-
NetworkName: {},
268+
if opts.ProjectNetwork == "" {
269+
return "", fmt.Errorf("project network is required")
270+
}
271+
if err := d.EnsureNetwork(ctx, opts.ProjectNetwork); err != nil {
272+
return "", fmt.Errorf("failed to ensure project network: %w", err)
273+
}
274+
275+
endpoints := map[string]*network.EndpointSettings{
276+
opts.ProjectNetwork: {
277+
Aliases: opts.InternalAliases,
271278
},
272279
}
280+
if opts.AttachEdge {
281+
endpoints[NetworkName] = &network.EndpointSettings{}
282+
}
283+
284+
networkConfig := &network.NetworkingConfig{
285+
EndpointsConfig: endpoints,
286+
}
273287

274288
// Create container
275289
resp, err := d.client.ContainerCreate(ctx, config, hostConfig, networkConfig, nil, opts.ContainerName)
@@ -286,6 +300,25 @@ func (d *DockerService) RunContainer(ctx context.Context, opts RunContainerOptio
286300
return resp.ID, nil
287301
}
288302

303+
// EnsureNetwork creates a Docker network if it does not already exist.
304+
func (d *DockerService) EnsureNetwork(ctx context.Context, name string) error {
305+
_, err := d.client.NetworkInspect(ctx, name, network.InspectOptions{})
306+
if err == nil {
307+
return nil
308+
}
309+
310+
_, err = d.client.NetworkCreate(ctx, name, network.CreateOptions{})
311+
if err != nil {
312+
// Another deploy may have created the network in the meantime.
313+
if _, inspectErr := d.client.NetworkInspect(ctx, name, network.InspectOptions{}); inspectErr == nil {
314+
return nil
315+
}
316+
return err
317+
}
318+
319+
return nil
320+
}
321+
289322
// StopContainer stops a running container.
290323
func (d *DockerService) StopContainer(ctx context.Context, containerID string) error {
291324
timeout := 30
@@ -409,11 +442,14 @@ type DomainConfig struct {
409442

410443
// RunContainerOptions holds options for running a container.
411444
type RunContainerOptions struct {
412-
ImageName string
413-
ContainerName string
414-
PodID string
415-
Domains []DomainConfig
416-
EnvVars map[string]string
445+
ImageName string
446+
ContainerName string
447+
PodID string
448+
Domains []DomainConfig
449+
EnvVars map[string]string
450+
ProjectNetwork string
451+
AttachEdge bool
452+
InternalAliases []string
417453
}
418454

419455
func mapToEnvSlice(m map[string]string) []string {

0 commit comments

Comments
 (0)