Containerization guide
Prerequisites: Application requirements
This document explains how to package your application for the Clouve marketplace. You will create two files: a docker-compose.yml for local development, and a clv-docker-compose.yml, the marketplace manifest, for platform deployment.
Overview
Clouve uses a two-file system:
| File | Purpose | Used by |
|---|---|---|
docker-compose.yml | Local development and testing | You, during development |
clv-docker-compose.yml | Marketplace deployment manifest | The Clouve platform |
The marketplace manifest is a superset of Docker Compose. It adds x-clouve-* extension fields that tell the platform how to generate Kubernetes resources. Standard Docker Compose tooling ignores these fields, so you can also use the file locally.
Dockerfile best practices
Before writing the manifest, make sure your Docker images follow these practices:
Use specific base image versions
# Good
FROM php:8.3-apache
FROM node:22-alpine
FROM python:3.12-slim
# Bad - unpredictable across rebuilds
FROM php:latest
FROM node:alpine
Keep images small
# Use multi-stage builds for compiled languages
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o app .
FROM alpine:3.20
COPY --from=builder /app/app /usr/local/bin/app
CMD ["app"]
Avoid running as root
RUN useradd -m -u 1001 appuser
USER appuser
Handle signals properly
Use the exec form for CMD/ENTRYPOINT so signals reach your process instead of a shell:
# Good - signals reach the process
ENTRYPOINT ["node", "server.js"]
# Bad - signals go to sh, not node
ENTRYPOINT node server.js
docker-compose.yml: the local development file
This file is for testing your application locally. It does not need any Clouve-specific fields.
Example: WordPress
version: "3.8"
services:
wordpress:
image: wordpress:6.7.1
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: wordpress-mariadb
WORDPRESS_DB_NAME: wordpress
WORDPRESS_DB_USER: wpuser
WORDPRESS_DB_PASSWORD: devpassword
volumes:
- wordpressdata:/var/www/html/wp-content
depends_on:
- wordpress-mariadb
wordpress-mariadb:
image: mariadb:10.11
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_DATABASE: wordpress
MYSQL_USER: wpuser
MYSQL_PASSWORD: devpassword
volumes:
- dbdata:/var/lib/mysql
volumes:
wordpressdata:
dbdata:
Test it:
docker-compose up
# Open http://localhost:8080
clv-docker-compose.yml: the marketplace manifest
The marketplace manifest extends Docker Compose with four x-clouve-* extension blocks that must be present on every service.
Extension blocks reference
x-clouve-metadata: # Required on every service
containerName: string # Must match the service key
purpose: string # Frontend | Backend | Database | Cache | Message Queue
protocol: string # TCP | UDP
isPublic: boolean # true = publicly accessible via Ingress
memoryBase: number # Memory in GB (gibibytes). Minimum 1, decimals allowed (e.g., 0.5).
cpuBase: number # CPU in decimal cores. Minimum 0.5 (e.g., 0.5 = half a core).
x-clouve-healthcheck: # Required on every service (set enabled: false to disable)
enabled: boolean
type: string # HTTP | TCP | Command
path: string # HTTP path for HTTP, "" for TCP, command string for Command
port: number # 0 = use containerPort
initialDelay: number # Seconds before first check
interval: number # Seconds between checks
timeout: number # Seconds before check times out
failureThreshold: number # Failures before marking unhealthy
successThreshold: number # Successes before marking healthy
x-clouve-environment-types: # Required (use {} if no env vars)
VAR_NAME: type # Maps each env var name to its type
x-clouve-volumes: # Required if service has volumes (omit if none)
- name: string # Must match the volume key
size: string # e.g., "10Gi", "100Gi". Minimum "8Gi".
description: string # Optional
# Public-container-only: supplies bundle metadata for multi-app submissions.
# Optional on single-app submissions. Round-trips through download/upload.
x-clouve-bundle-metadata:
appVersion: string # Semantic version of this app (e.g., "4.3.0")
appTitle: string # Display title in the marketplace
appDescription: string # Marketing description
appIcon: string # URL of the icon image
adminPath: string # Optional URL path that opens the app's admin UI
# Top-level (sibling of `services:`, not nested in a service).
# Opts the application into the platform-synthesized AI Assistant sidecar.
# See [Magneto Agent / AI Assistant](./11-magneto-agent.md).
x-clouve-agent:
enabled: boolean
skills:
url: string # Skills repo URL (required when enabled)
git:
token: string # Optional; rendered as a `secret`-typed env var
username: string # Optional
advanced:
client: string|null # e.g., "claude-code"; null = pick at runtime
isPublic: boolean # Whether the agent has its own subdomain
sidecarHosts: array|null # null = auto-derive from siblings
sidecarHostsExclude: array # Container names to drop from auto-derived list
sidecarPullTimeout: number # Seconds; max 1800
Complete example: WordPress marketplace manifest
version: "3.8"
services:
wordpress:
image: wordpress:6.7.1
ports:
- "80:80"
environment:
WORDPRESS_DB_HOST: wordpress-mariadb
WORDPRESS_DB_NAME: wordpress
WORDPRESS_DB_USER: wpuser
WORDPRESS_DB_PASSWORD: changeme
WORDPRESS_SITE_URL: ""
WORDPRESS_ADMIN_USER: admin
WORDPRESS_ADMIN_PASSWORD: changeme
WORDPRESS_ADMIN_EMAIL: admin@example.com
volumes:
- wordpressdata:/var/www/html/wp-content
x-clouve-metadata:
containerName: wordpress
purpose: Frontend
protocol: TCP
isPublic: true
memoryBase: 1 # 1 GB
cpuBase: 1
x-clouve-healthcheck:
enabled: true
type: HTTP
path: /
port: 80
initialDelay: 30
interval: 10
timeout: 5
failureThreshold: 3
successThreshold: 1
x-clouve-environment-types:
WORDPRESS_DB_HOST: containerReference
WORDPRESS_DB_NAME: static
WORDPRESS_DB_USER: static
WORDPRESS_DB_PASSWORD: secret
WORDPRESS_SITE_URL: applicationUrl
WORDPRESS_ADMIN_USER: applicationUsername
WORDPRESS_ADMIN_PASSWORD: applicationPassword
WORDPRESS_ADMIN_EMAIL: userConfigurable
x-clouve-volumes:
- name: wordpressdata
size: 10Gi
description: WordPress uploads and theme files
wordpress-mariadb:
image: mariadb:10.11
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: rootchangeme
MYSQL_DATABASE: wordpress
MYSQL_USER: wpuser
MYSQL_PASSWORD: changeme
volumes:
- dbdata:/var/lib/mysql
x-clouve-metadata:
containerName: wordpress-mariadb
purpose: Database
protocol: TCP
isPublic: false
memoryBase: 1 # 1 GB
cpuBase: 0.5
x-clouve-healthcheck:
enabled: true
type: TCP
path: ""
port: 3306
initialDelay: 30
interval: 10
timeout: 5
failureThreshold: 3
successThreshold: 1
x-clouve-environment-types:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: static
MYSQL_USER: static
MYSQL_PASSWORD: secret
x-clouve-volumes:
- name: dbdata
size: 10Gi
description: MariaDB database files
volumes:
wordpressdata:
dbdata:
Environment variable types in context
The x-clouve-environment-types block maps each variable name to a type. The type determines how the platform handles the variable at deployment time.
| When your variable is... | Use type |
|---|---|
| A fixed config value (log level, debug flag) | static |
| A password, API key, or secret | secret |
| Something the user should customize (email, URL) | userConfigurable |
| Another container's hostname | containerReference |
| An admin username for the app | applicationUsername |
| An admin password for the app | applicationPassword |
| The app's own public URL (auto-generated) | applicationUrl |
| The app's own hostname (auto-generated) | applicationHost |
Full reference: Environment variables →
Container ordering
The order of containers in the services section determines their deployment sequence. List them in dependency order:
services:
# 1. Database first (others depend on it)
wordpress-mariadb: ...
# 2. Cache second (if any)
wordpress-redis: ...
# 3. Application last (depends on database being ready)
wordpress: ...
Kubernetes does not guarantee strict ordering, but databases and caches typically start faster and will be ready by the time application containers finish initializing. Always implement retry/backoff logic in your application for external dependencies.
Building and pushing images
If your application uses a custom Docker image (not a public official image):
# Build for multiple platforms (required for Clouve)
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag myregistry/my-app:1.0.0 \
--push \
./image/
# Or using the magneto build script
cd magneto
./build.sh my-application --push
The magneto/build.sh script handles multi-platform builds automatically:
# Single platform (local testing)
./build.sh my-application
# Multi-platform + push to registry
./build.sh my-application --push
Validation
Before uploading your manifest, validate it:
# Check YAML syntax
yamllint clv-docker-compose.yml
# Test that docker-compose can parse it (ignores x-clouve-* fields)
docker-compose -f clv-docker-compose.yml config
Common mistakes
| Mistake | Symptom | Fix |
|---|---|---|
containerName doesn't match service key | Deployment misconfiguration | Set containerName to exactly the service key |
Missing x-clouve-metadata on a service | Submission validation error | Add the block to every service |
isPublic: true on the database | Security risk + incorrect setup | Only the app frontend/backend should be public |
Volume declared in service but missing in volumes: section | Docker Compose parse error | Add a matching entry in the top-level volumes: |
Using memoryBase: 512 thinking it means Mi | 512 GB requested and rejected | memoryBase is in GB: use 0.5, 1, 2 |
Using cpuBase: 500m instead of cpuBase: 0.5 | Wrong resource allocation | Use decimal cores: 0.5, 1, 2. Minimum 0.5. |
Using memoryBase: 0.25 thinking it's allowed | Validator rejects (< minimum 1) | Memory minimum is 1 (= 1 GB) |
Using size: 1Gi for a volume | Validator rejects (< 8 Gi) | Volume minimum is 8Gi |
Native Docker healthcheck block
When health checks are enabled on a service, the downloader also emits a standard Docker Compose healthcheck: block alongside the x-clouve-healthcheck block. This lets the manifest work correctly when run locally with docker-compose up: the same probe runs against the local container. The mapping is:
| Clouve type | Docker test command |
|---|---|
HTTP | ["CMD", "wget", "--spider", "-q", "http://localhost:<port><path>"] |
TCP | ["CMD", "nc", "-z", "localhost", "<port>"] |
Command | ["CMD-SHELL", "<command from path>"] |
You do not need to write this block yourself. It is generated from the x-clouve-healthcheck values during download and re-derived on upload.
Next steps
- Container configuration →: how to configure containers in the UI (Step 2 of the wizard)
- Environment variables →: all 8 variable types in detail
- Health checks →: configuring liveness and readiness checks
- Magneto Agent / AI Assistant →: adding an AI Assistant sidecar to your application