Multi-Container Builds¶
Most real-world applications have more than one piece: a frontend and a backend, or an API server and a background worker. AppMecca lets you build all of them from a single Git repository in one build run.
Why Multi-Container?¶
Instead of maintaining separate repositories for your frontend and backend, you can keep everything in one repo (a monorepo) and let AppMecca build all the images at once.
Benefits:
- One build command builds everything
- Atomic versioning — all containers share the same Git commit hash
- Easier coordination — frontend and backend changes ship together
- Simpler repository management — one repo, one set of CI/CD, one code review
How It Works¶
When you define multiple entries in the containers section of appmecca.yaml, AppMecca builds them all sequentially in a single build run. Each container gets its own repository in the private image registry and its own image tag.
containers:
- name: frontend
dockerfile: frontend/Dockerfile
context: frontend/
- name: backend
dockerfile: backend/Dockerfile
context: backend/
deploy:
- name: frontend
container: frontend
port: 3000
- name: backend
container: backend
port: 8080
When you trigger a build:
AppMecca will:
- Clone the repository
- Read
appmecca.yaml - Build
frontendusingfrontend/Dockerfile - Push
frontendto the image registry asappmecca/dev/myapp/frontend:a1b2c3d - Build
backendusingbackend/Dockerfile - Push
backendto the image registry asappmecca/dev/myapp/backend:a1b2c3d
Both images get the same commit hash tag because they were built from the same commit.
Expected output
[Build] Cloning repository...
[Build] Reading appmecca.yaml...
[Build] Building container 1/2: frontend
[Build] Pushing image: appmecca/dev/myapp/frontend:a1b2c3d
[Build] Building container 2/2: backend
[Build] Pushing image: appmecca/dev/myapp/backend:a1b2c3d
Build completed successfully in 6m 15s.
Image Repository Naming¶
Each container gets its own repository in the private image registry, following the pattern:
| Container name | App | Environment | Image repository |
|---|---|---|---|
frontend |
myapp |
dev |
appmecca/dev/myapp/frontend |
backend |
myapp |
dev |
appmecca/dev/myapp/backend |
worker |
myapp |
staging |
appmecca/staging/myapp/worker |
api |
myapp |
prod |
appmecca/prod/myapp/api |
Container names become platform resource names
Keep container names short and lowercase. They are used in image repository names, routing resource names, and other platform resources that have character length limits. Use hyphens, not underscores.
Complete Example: Full-Stack App¶
Here is a full appmecca.yaml for a typical frontend + backend application:
# Build configuration
containers:
- name: frontend
dockerfile: frontend/Dockerfile
context: frontend/
args:
NODE_ENV: production
- name: backend
dockerfile: backend/Dockerfile
context: backend/
# Deployment configuration
deploy:
- name: frontend
container: frontend
port: 3000
health_check:
path: /
environment:
API_URL: "${service:backend:internal}"
- name: backend
container: backend
port: 8080
health_check:
path: /health
# Enable PostgreSQL for the backend
database:
enabled: true
This gives you:
https://myapp-frontend-yourgroup.dev.yourdomain.io— serves the frontendhttps://myapp-backend-yourgroup.dev.yourdomain.io— serves the API- The frontend's
API_URLenvironment variable automatically points to the backend's URL
Every service gets its own URL
A service hostname always contains the service name and your group slug — there is no bare myapp.dev.… URL, even for a single-service app. Run mecca app show myapp to see the real URLs for your app.
The container field links build to deploy
Notice how deploy[].container references containers[].name. This is how AppMecca knows which image to use for each service. The names must match exactly.
Repository Layouts¶
AppMecca is flexible about how you organize your repository. Here are the most common patterns.
Pattern 1: Separate Directories (Recommended)¶
Each service has its own top-level directory with its own Dockerfile:
myapp/
├── appmecca.yaml
├── frontend/
│ ├── Dockerfile
│ ├── package.json
│ └── src/
│ └── ...
└── backend/
├── Dockerfile
├── requirements.txt
└── src/
└── ...
containers:
- name: frontend
dockerfile: frontend/Dockerfile
context: frontend/
- name: backend
dockerfile: backend/Dockerfile
context: backend/
deploy:
- name: frontend
container: frontend
port: 3000
- name: backend
container: backend
port: 8080
Each Dockerfile only sees files from its own directory. This keeps build contexts small and builds fast.
Pattern 2: Services Directory¶
For projects with many services, a services/ directory keeps things organized:
myapp/
├── appmecca.yaml
└── services/
├── web/
│ ├── Dockerfile
│ └── ...
├── api/
│ ├── Dockerfile
│ └── ...
└── worker/
├── Dockerfile
└── ...
containers:
- name: web
dockerfile: services/web/Dockerfile
context: services/web/
- name: api
dockerfile: services/api/Dockerfile
context: services/api/
- name: worker
dockerfile: services/worker/Dockerfile
context: services/worker/
deploy:
- name: web
container: web
port: 3000
- name: api
container: api
port: 8080
- name: worker
container: worker
# No port = internal service, no routing, no URL
Pattern 3: Shared Code (Root Context)¶
When your services share code (like a common library or shared types), set the build context to the repository root:
myapp/
├── appmecca.yaml
├── shared/
│ └── models.py
├── frontend/
│ ├── Dockerfile
│ └── ...
└── backend/
├── Dockerfile
└── ...
containers:
- name: frontend
dockerfile: frontend/Dockerfile
context: .
- name: backend
dockerfile: backend/Dockerfile
context: .
deploy:
- name: frontend
container: frontend
port: 3000
- name: backend
container: backend
port: 8080
With context: ., both Dockerfiles can COPY from anywhere in the repository, including the shared/ directory:
# backend/Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY shared/ ./shared/
COPY backend/ ./backend/
RUN pip install -r backend/requirements.txt
CMD ["python", "-m", "backend.main"]
Root context means larger builds
When you set context: ., Docker sends the entire repository to the build engine. This is fine for small repos, but can slow down builds for large ones. If your services don't actually share code, use separate contexts (Pattern 1) for faster builds.
One Image, Multiple Services¶
You don't always need multiple containers. If your frontend and backend use the same language and runtime, you can build one image and run it with different commands:
containers:
- name: myapp
dockerfile: Dockerfile
context: .
deploy:
- name: web
container: myapp
port: 8000
command: "gunicorn app:application -w 4 -b 0.0.0.0:8000"
- name: worker
container: myapp
command: "celery -A app worker -l info"
- name: scheduler
container: myapp
command: "celery -A app beat -l info"
This builds one image and runs three services from it. The command field overrides the Dockerfile CMD for each service. Only the web service gets a URL (because it has a port); worker and scheduler run in the background.
When to use one image vs multiple
- One image, multiple services: Great for Python apps where the web server, worker, and scheduler all share the same codebase (Django + Celery is the classic example)
- Multiple images: Necessary when services use different languages or runtimes (e.g., a React frontend and a Python backend)
Build Order¶
When you have multiple containers, AppMecca builds them in the order they appear in appmecca.yaml. All containers are built in sequence within a single build run.
containers:
- name: frontend # Built first
dockerfile: frontend/Dockerfile
context: frontend/
- name: backend # Built second
dockerfile: backend/Dockerfile
context: backend/
deploy:
- name: frontend
container: frontend
port: 3000
- name: backend
container: backend
port: 8080
If any container fails to build, the entire build is marked as failed and no images are pushed. This ensures you never end up with a new frontend but an old backend.
All or nothing
You cannot build just one container from a multi-container appmecca.yaml. When you trigger a build, all containers are built. This ensures all images in a deployment are from the same commit.
Passing Build Arguments Between Containers¶
Sometimes your frontend needs to know the backend's URL at build time (for example, to embed it in a static JavaScript bundle). You can use inter-service URL templates as build arguments:
containers:
- name: frontend
dockerfile: frontend/Dockerfile
context: frontend/
args:
PUBLIC_API_URL: "${service:backend}"
- name: backend
dockerfile: backend/Dockerfile
context: backend/
deploy:
- name: frontend
container: frontend
port: 3000
- name: backend
container: backend
port: 8080
In your frontend Dockerfile:
FROM node:20-slim AS builder
WORKDIR /app
ARG PUBLIC_API_URL
ENV VITE_API_URL=$PUBLIC_API_URL
COPY . .
RUN npm ci && npm run build
FROM nginx:alpine
COPY --from=builder /app/dist/ /usr/share/nginx/html/
The ${service:backend} template resolves to the backend's external HTTPS URL (e.g., https://myapp-backend.dev.yourdomain.io) at build time.
nginx and dropped Linux capabilities
A stock nginx image starts as root, chowns its cache directories,
then drops to an unprivileged user. Containers run with all Linux
capabilities dropped, so that sequence only works when a limited set is
restored — on container port 80/443, or on any declared port when the image
runs as root and declares an EXPOSE.
If your container exits with
nginx: [emerg] chown(...) failed (1: Operation not permitted), that is the
cause. The most robust fix is to make the image not need the capability at
all:
Troubleshooting Multi-Container Builds¶
Build fails on the second container¶
Read the logs to see which container failed and why:
The logs show which container is being built (Building container 1/2: frontend, Building container 2/2: backend), so you can find the failing step.
"context" files not found¶
If your Dockerfile tries to COPY a file and gets a "not found" error, the file is probably outside the build context.
- If the file is in a sibling directory, change
contextto.(repo root) - Make sure paths in
COPYare relative to the context, not the Dockerfile location
Container name conflicts¶
Container names must be unique within your appmecca.yaml. If two containers have the same name, the build will fail.
Next Steps¶
- Containers Configuration — Full reference for
containersoptions - Services Configuration — Configure how containers run (ports, health checks, commands)
- Inter-Service Communication — How services discover and talk to each other
- Deploying Your App — Deploy your multi-container app