Compose & multi-service apps
Run an app that's really several pieces at once — web, worker, database — from a docker-compose file or added by hand, and keep every service in sync.
Some apps aren't one thing — they're a team. A website out front, a background worker doing slow jobs, and a database holding the data. Think of a restaurant: the dining room, the kitchen, and the pantry are three separate stations, but together they're one restaurant. Alaf Server calls a set of services that run together a stack, and it can run the whole stack for you.
Each service in the stack is its own little box (a container) with its own image, ports, storage, and settings — but you manage them all from one place: the project's Services tab.
What you need first
- A project in Alaf Server. A stack is just a project that has more than one service.
- Your services either come from a
docker-compose.ymlfile in your repo, or you add them by hand in the dashboard. Both are covered below. - Nothing else — no Docker knowledge required to add a service from the catalog.
Where your services live
Open your project and choose the Services tab. You'll see one row per service. Each row tells you, at a glance:
- The service name (e.g.
web,worker,db). - A Public or Internal badge — Public means it has a web address people can visit; Internal means only the other services can reach it (a database is almost always Internal).
- A status dot: Running, Stopped, Starting, Failed, or Disabled.
Click any row to open that service's full screen. If the repo changed a value you'd edited, the row also shows an Upstream change badge — more on that in Drift.
Screenshot
How services get into a stack
There are two ways, and you can mix them.
1. From a docker-compose.yml file
If your repo has a compose file, Alaf Server reads it and turns each service in the file into a service row —
its image (or a build context and Dockerfile), ports, volumes, environment, command, restart policy,
depends_on links, and healthcheck. From then on those rows are what Alaf Server deploys.
Alaf Server tracks the compose fields it models: image, build, dockerfile, ports, depends_on,
environment, volumes, command, restart, and healthcheck. Everything else in your file — networks,
build args, labels, profiles, comments — is left untouched: Alaf Server neither reads it nor rewrites it.
Alaf Server never writes to your repo
Edits you make in the dashboard are stored by Alaf Server and used on every deploy. Your docker-compose.yml is
never changed. If you want the file itself to reflect a change (so a teammate or a fresh clone sees it), edit
the file in your repo and push.
2. Added by hand
Any project can get extra services from a built-in catalog of common images — databases, caches, search, queues, storage, and more — or from any Docker image you name.
Open the Add Service dialog
On the Services tab, press Add Service.
Pick a service
You land on a catalog. Browse a category on the left (Databases, Caches, Search, Vector & AI, Queues & Streams, Object Storage, Auth & Identity, Mail & SMTP, Runtimes), search by name, or choose Custom image to type any image tag yourself. Click the one you want.
Screenshot
Fill in the details
Alaf Server pre-fills what it can. Check these:
- Service name — short and unique within the stack (e.g.
db). - Ports — the port(s) the container listens on, comma-separated (e.g.
5432). - Image — the Docker image and tag (e.g.
postgres:16-alpine). - Persistent volumes — optional
name → pathpairs so data survives restarts. - Environment variables — settings the service needs.
- Routing — leave it Internal only (the right choice for databases and background workers), or switch it to Publicly exposed to give the service its own web address — a free subdomain or your own custom domain.
Press Add service.
It deploys automatically
If the project already has a live deployment, adding a service redeploys the project so the new container actually starts — you'll see a "deploying…" message. If the project has never been deployed yet, the service is just saved, and you deploy the project yourself to bring it up.
Re-syncing after the compose file changes
You don't normally sync by hand — on every deploy (and on every push, for GitHub-backed projects) Alaf Server
re-reads the repo's current docker-compose.yml and reconciles it against your service rows
automatically. From the terminal you can also force a sync from a specific file:
# Rebuild the stack's service rows from a compose file.
# Services in the stack but NOT in the file are removed — you'll be asked to confirm.
alaf server service sync --project my-app ./docker-compose.ymlSync needs Docker Compose v2
alaf server service sync shells out to docker compose config to parse and validate the file, so Docker
Compose v2 must be installed where you run the command. If docker isn't found, the sync stops with a clear
message.
Opening a service
Click a service row to open its screen. Across the top is a row of tabs:
- Overview — read-only facts, each shown only when it applies: Network (the internal address other services use to reach this one, the container ports, the published host port, the current IP, and the container ID — shown as a Workspace ID on Alaf Server Cloud), Configuration (restart policy, command, depends-on), and Volumes.
- Terminal — a live shell inside the container. It only opens while the service is Running ("Start the service to open a shell." until then).
- Logs — the service's output, streamed as it happens.
- Environment — the service's environment variables. Edit them and press Save environment.
- Settings — the build and runtime fields you can change: name, image (or a build context and Dockerfile), ports, depends-on, volumes, command, restart policy, and an optional healthcheck — plus the enable toggle, the lifecycle buttons, and Delete. (Environment variables have their own Environment tab.)
- Backup — schedule and run backups. This tab shows only for compose services (the stateful containers), not for source-built sub-apps.
Screenshot
Start, stop, restart
The lifecycle buttons live on the service's Settings tab. Which ones appear depends on the state:
- Running → Stop and Restart.
- Stopped → Start.
- Disabled (never started) → Enable & start, which turns the service on and deploys it.
- Enabled with a live deployment → Redeploy, which rebuilds only this service and leaves the rest of the stack running.
There's also Disable Service / Enable Service (a disabled service is skipped on deploy) and Delete (tears the service down).
Open the service → Settings tab → press Start, Stop, or Restart.
alaf server service start --project my-app db
alaf server service stop --project my-app db
alaf server service restart --project my-app db
# See what's actually running
alaf server service containers --project my-appTarget a service by its name or its svc_… id.
When the repo and your edits disagree: drift
Here's the tricky bit, made simple. You can edit a service in the dashboard and keep a
docker-compose.yml in your repo. What happens when both change the same value?
On each deploy Alaf Server does a three-way compare for every service:
- base — the compose values it last read in,
- yours — the service's current values (including your dashboard edits),
- theirs — what the repo file says right now.
| Repo changed? | You edited it? | What Alaf Server does |
|---|---|---|
| no | — | nothing — your values stand |
| yes | no | applies the repo's value automatically |
| yes | yes | keeps your value and flags it as drift for you to review |
| new service in the repo | — | adds it automatically |
| removed from the repo | no | removes it |
| removed from the repo | yes | keeps it — your work is never silently deleted |
So services you never touched track the repo on their own, and your edits are never overwritten without
asking. When a value you'd edited changes in the repo, the Services tab shows an amber drift banner with a
per-field from → to diff and two choices per service:
- Accept upstream — take the repo's value and move your baseline to it.
- Keep mine — keep your value; the flag clears so it stops nagging, but your edit stays in effect.
Screenshot
from → to field diffs, and the Keep mine / Accept upstream buttons. (screenshot pending)On the Services tab, use the drift banner's Keep mine or Accept upstream button for each service.
# Take the repo's values, discarding your edits
alaf server service drift accept --project my-app web
# Keep your edits and stop flagging the change
alaf server service drift keep --project my-app webPorts & volumes: cloud vs self-hosted
How ports and volumes behave depends on where the stack runs:
- Self-hosted / your own server (Docker): full compose semantics.
host:containerport mappings are published on the host, and volumes (named or bind mounts) are mounted. - Alaf Server Cloud: only the container port matters — it's routed to your domain through the edge proxy, and a published host port is ignored. Docker volume mounts don't apply. A cloud service's data lives on its workspace disk, which survives restarts but is not carried across a rebuild unless you turn on backups.
You'll see this in the interface too. When you set the stack up in the deploy wizard, a cloud project greys out the published (host) port and the volume fields and notes that only the container port is routed. Stateful images — databases like Postgres, MySQL, MariaDB, MongoDB, Redis or Elasticsearch, and any service that declares a volume — get an amber reminder that their data isn't kept across a rebuild unless you turn on backups. Later, on the Services tab, a cloud service's Overview shows a Workspace ID in place of the Container ID.
Prefer the terminal?
Every service action has a CLI command. All of them target a stack with -p / --project (id, slug, or name)
and a service by name or svc_… id.
alaf server service list --project my-app # list the services
alaf server service get --project my-app db # show one service's config
alaf server service create --project my-app cache \
--image redis:7 --port 6379 # add a service by hand
alaf server service sync --project my-app ./docker-compose.yml
alaf server service start --project my-app web # start / stop / restart
alaf server service logs --project my-app web --follow # stream logs
alaf server service env set --project my-app web KEY=value # set env vars (--secret, -e <env>)
alaf server service drift accept --project my-app web # resolve drift
alaf server service delete --project my-app cache # remove a serviceIf something goes wrong
I added a service but nothing is running
Adding a service saves it, but a container only starts on a deploy. If the project already had a live deployment, Alaf Server redeploys for you; if it didn't, open the project and deploy it (or use the service's Enable & start button on its Settings tab).
`alaf server service sync` says docker wasn't found
The sync command uses Docker Compose v2 to read your file. Install Docker (which includes docker compose)
on the machine where you run the command, then try again. You can always add or edit services in the
dashboard instead.
A service keeps re-flagging the same drift
That means the repo file and your dashboard edit still disagree. Pick one source of truth: press Accept
upstream to take the repo's value, or Keep mine to lock in your edit. To make the change permanent for
everyone, edit docker-compose.yml in your repo and push — once your value matches the repo, the drift
clears on its own.
What next?
Deploy from GitHub
Get your repo — compose file and all — online in the first place.
Core concepts
The plain-language vocabulary: projects, services, deployments, and runtimes.
Runtime model
The three places a stack can run — this machine, your server, or Alaf Server Cloud.
Data ownership
Where your service data lives when you self-host, and how backups protect it.
Environment variables & secrets
Give your app its settings and secrets — add them in the Configuration tab or the CLI, keep the sensitive ones masked, and apply changes without a full rebuild.
Preview environments
Make a separate, safe copy of your project from a git branch — so you can try changes before they reach your real site.