Skip to content

Inter-Service Communication

When your application has multiple services (e.g., a frontend and a backend), they often need to talk to each other. AppMecca provides a template syntax that automatically resolves service URLs at deploy time, so you never have to hardcode them.

The Problem

In a multi-service app, your frontend needs to know the backend's URL. But the URL depends on the app slug, service name, owning group, branch, and environment domain -- things that change between environments and branches. Hardcoding https://myapp-backend-acme.dev.appmecca.net would break the moment you deploy to staging, or on a feature branch.

The Solution: Service Templates

In your appmecca.yaml, use template variables in the environment section:

containers:
  - name: frontend
    dockerfile: frontend/Dockerfile
    context: frontend/
  - name: backend
    dockerfile: backend/Dockerfile
    context: backend/

deploy:
  - name: frontend
    container: frontend
    port: 3000
    environment:
      API_URL: "${service:backend:internal}"

  - name: backend
    container: backend
    port: 8080
    health_check:
      path: /health

AppMecca replaces the template with the actual address of the backend service when it deploys the container. ${service:backend:internal} becomes a private address inside your app's own network; ${service:backend} becomes the public URL — under path routing an app has ONE hostname and each service is mounted on a path beneath it, so in dev that is https://myapp-acme.dev.appmecca.net/api and in staging https://myapp-acme.staging.appmecca.net/api.

Your frontend code just reads the API_URL environment variable -- no URL construction needed.

Two Types of URLs

AppMecca supports two template formats, and which one is correct depends on who reads the value:

  • a value a browser reads — anything baked into a client bundle, or set on a type: static / type: spa service — must be the external form; a browser cannot reach the private network.
  • a value one container uses to call another — an SSR frontend calling its API, a worker calling a service — should be the internal form: it stays inside the private network and never leaves the platform.

External URL (HTTPS, public) — the one to use

${service:name}

Resolves to the public HTTPS URL routed through the AppMecca HTTPS router:

https://{app}-{service}-{group}.{env}.{domain}

On a non-production branch, the branch name is included automatically:

https://{app}-{branch}-{service}-{group}.{env}.{domain}

You do not need to change your appmecca.yaml templates -- AppMecca resolves them to the correct branch-scoped URL automatically.

Use this when:

  • A browser-based frontend calls a backend API
  • A service needs to be reachable from outside the platform
  • You want the traffic to go through the platform router (health checks, TLS, routing rules)

Internal URL (HTTP, private)

${service:name:internal}

Resolves to a private HTTP address on AppMecca's internal DNS:

http://{service}.{app}.{env}.appmecca.internal:{host-port}

The port is the host port AppMecca allocated for the target container, not the container port you declared in appmecca.yaml.

The address is stable across redeploys

This page used to carry a warning that the internal address went stale whenever the target service was redeployed, because the host port was re-allocated each time. That is no longer true. The port is now frozen as the service's permanent address and the platform forwards it to whichever container is live, so an internal URL baked into your container keeps working across the target's redeploys.

It is not reachable from a browser, and it never will be — that is the point of it. Use the external form for anything a browser loads.

Template Reference

Template Resolves To Notes
${service:backend} https://myapp-acme.dev.appmecca.net/api Public HTTPS URL — use for anything a browser loads. The path is the service's path: mount
${service:frontend} https://myapp-acme.dev.appmecca.net The front door owns /, so it has no mount segment
${service:backend:internal} http://backend.myapp.dev.appmecca.internal:7912 Private HTTP URL — use for container-to-container calls

The exact hostname depends on your app slug, owning group and branch, so treat the examples as shape rather than as literal values.

Unknown service names are rejected before you deploy

The name in ${service:name} must match a service name in your deploy section. A typo is caught during the repository check that runs at mecca register and mecca build trigger, and the error lists the service names it did find — you will not discover it at deploy time.

Complete Example

Here is a full appmecca.yaml for a Flask backend + Vue frontend, showing both template types:

containers:
  - name: backend
    dockerfile: backend/Dockerfile
    context: backend/

  - name: frontend
    dockerfile: frontend/Dockerfile
    context: frontend/

deploy:
  - name: backend
    container: backend
    port: 5000
    path: /api
    health_check:
      path: /api/health

  - name: frontend
    container: frontend
    port: 80
    primary: true
    health_check:
      path: /health
    environment:
      # Browser calls backend via public HTTPS
      PUBLIC_API_URL: "${service:backend}"

database:
  enabled: true

In the frontend container, the PUBLIC_API_URL environment variable will contain the backend's public HTTPS URL. Your frontend JavaScript can read this at runtime and use it for API calls.

When Templates Are Resolved

Templates are resolved at two points in the lifecycle:

  1. Build time -- External (${service:name}) templates are resolved and passed as Docker --build-arg values. This lets you bake the URL into your frontend bundle if needed.

  2. Deploy time -- Both external and internal templates are resolved and injected as environment variables into the running container. Internal templates are only resolved at deploy time because the host port is not known until then.

Internal templates are skipped at build time

The ${service:name:internal} template is not available during docker build because the host port has not been allocated yet. If you need inter-service URLs at build time (e.g., for a frontend bundle), use the external template.

Using Templates in Your Code

Frontend (JavaScript/React/Vue/Svelte)

For client-side apps, the URL needs to be available in the browser. A common pattern is to read it at build time via a build argument:

ARG PUBLIC_API_URL
ENV VITE_API_URL=$PUBLIC_API_URL

Then in your JavaScript:

const API_URL = import.meta.env.VITE_API_URL;

fetch(`${API_URL}/api/items`)
  .then(response => response.json())
  .then(data => console.log(data));

Backend (Python/Node.js/Go)

For server-side code, read the environment variable at runtime:

import os

INTERNAL_AUTH_URL = os.environ.get("AUTH_SERVICE_URL")
# Makes HTTP request to the auth service via internal network
response = requests.get(f"{INTERNAL_AUTH_URL}/verify")

Common Patterns

Frontend calls backend (browser-side)

environment:
  API_URL: "${service:backend}"

The frontend JavaScript running in the user's browser makes HTTPS requests to the backend through the public AppMecca HTTPS router.

Backend calls another backend (server-side)

environment:
  AUTH_SERVICE: "${service:auth}"

Use the external form here too. The internal form is faster but goes stale after the callee redeploys — see the warning above.

Multiple service references

environment:
  API_URL: "${service:backend}"
  WEBSOCKET_URL: "${service:realtime}"
  ASSETS_URL: "${service:media}"

You can reference as many services as you need — each name must be a service declared in the same deploy section.

Troubleshooting

Template not resolving

If you see the literal string ${service:backend} in your container instead of a URL:

  • Make sure the service name in the template matches a service name in your deploy section exactly
  • Verify the referenced service has a port defined (services without ports don't get URLs)
  • Check that you deployed (not just built) the application

Connection refused on an internal URL

This is the expected symptom of the known limitation above: the target service has been redeployed since your container started, so the host port baked into the URL no longer exists.

  • The fix is to switch to ${service:name}. Redeploying the calling service also works, but only until the callee is next deployed.
  • If you have only just deployed both, confirm the target service is running and that both are in the same environment and branch.

Next Steps