diff --git a/package.json b/package.json
index 392c46b3..b16273d5 100644
--- a/package.json
+++ b/package.json
@@ -24,6 +24,7 @@
     "lodash": "^3.0.0",
     "log-timestamp": "^0.1.2",
     "mixpanel": "0.0.20",
+    "mkdirp": "^0.5.1",
     "network-checker": "~0.0.5",
     "pinejs-client": "^1.7.1",
     "pubnub": "^3.7.13",
diff --git a/src/api.coffee b/src/api.coffee
index 63bd33ed..f118dce2 100644
--- a/src/api.coffee
+++ b/src/api.coffee
@@ -184,7 +184,7 @@ module.exports = (application) ->
 		utils.getKnexApp(appId)
 		.then (app) ->
 			res.status(200)
-			compose.up(application.composePath(appId), onStatus)
+			compose.up(appId, onStatus)
 			.catch (err) ->
 				console.log('Error on compose up:', err, err.stack)
 			.finally ->
@@ -202,7 +202,7 @@ module.exports = (application) ->
 		utils.getKnexApp(appId)
 		.then (app) ->
 			res.status(200)
-			compose.down(application.composePath(appId), onStatus)
+			compose.down(appId, onStatus)
 			.catch (err) ->
 				console.log('Error on compose down:', err, err.stack)
 			.finally ->
diff --git a/src/application.coffee b/src/application.coffee
index cd481cd6..273e7fa8 100644
--- a/src/application.coffee
+++ b/src/application.coffee
@@ -309,9 +309,6 @@ killmePath = (app) ->
 	appId = app.appId ? app
 	return "/mnt/root#{config.dataPath}/#{appId}/resin-kill-me"
 
-application.composePath = (appId) ->
-	return "/mnt/root#{config.dataPath}/#{appId}/docker-compose.yml"
-
 # At boot, all apps should be unlocked *before* start to prevent a deadlock
 application.unlockAndStart = unlockAndStart = (app) ->
 	lockFile.unlockAsync(lockPath(app))
diff --git a/src/compose.coffee b/src/compose.coffee
index 5ce45f85..7ec6ed2a 100644
--- a/src/compose.coffee
+++ b/src/compose.coffee
@@ -4,14 +4,23 @@ _ = require 'lodash'
 dockerUtils = require './docker-utils'
 { docker } = dockerUtils
 fs = Promise.promisifyAll(require('fs'))
-spawn = require('child_process').spawn
+{ spawn, execAsync } = Promise.promisifyAll(require('child_process'))
+mkdirp = Promise.promisify(require('mkdirp'))
+path = require 'path'
+utils = require './utils'
 
-runComposeCommand = (composeArgs, path, onStatus) ->
-	onStatus ?= console.log.bind(console)
-	reportStatus = (status) ->
-		try onStatus(status)
+composePathSrc = (appId) ->
+	return "/mnt/root#{config.dataPath}/#{appId}/docker-compose.yml"
+
+composePathDst = (appId) ->
+	return "/mnt/root#{config.dataPath}/resin-supervisor/compose/#{appId}/docker-compose.yml"
+
+composeDataPath = (appId, serviceName) ->
+	return "compose/#{appId}/#{serviceName}"
+
+runComposeCommand = (composeArgs, appId, reportStatus) ->
 	new Promise (resolve, reject) ->
-		child = spawn('docker-compose', ['-f', path].concat(composeArgs), stdio: 'pipe')
+		child = spawn('docker-compose', ['-f', composePathDst(appId)].concat(composeArgs), stdio: 'pipe')
 		.on 'error', reject
 		.on 'exit', (code) ->
 			return reject(new Error("docker-compose exited with code #{code}")) if code isnt 0
@@ -20,18 +29,30 @@ runComposeCommand = (composeArgs, path, onStatus) ->
 			reportStatus(status: '' + data)
 		child.stderr.on 'data', (data) ->
 			reportStatus(status: '' + data)
-	.catch (err) ->
-		msg = err?.message or err
-		reportStatus(error: msg)
-		throw err
+
+writeComposeFile = (composeSpec, dstPath) ->
+	mkdirp(path.dirname(dstPath))
+	.then ->
+		YAML.stringify(composeSpec)
+	.then (yml) ->
+		fs.writeFileAsync(dstPath, yml)
+	.then ->
+		execAsync('sync')
+
+validateServiceOptions = (service) ->
+	Promise.try ->
+		options = _.keys(service)
+		_.each options, (option) ->
+			throw new Error("Using #{option} is not allowed.") if !_.includes(utils.validComposeOptions, option)
 
 # Runs docker-compose up using the compose YAML at "path".
 # Reports status and errors in JSON to the onStatus function.
-exports.up = (path, onStatus) ->
+# Copies the compose file from srcPath to dstPath adding default volumes
+exports.up = (appId, onStatus) ->
 	onStatus ?= console.log.bind(console)
 	reportStatus = (status) ->
 		try onStatus(status)
-	fs.readFileAsync(path)
+	fs.readFileAsync(composePathSrc(appId))
 	.then (data) ->
 		YAML.parse(data.toString())
 	.then (composeSpec) ->
@@ -46,9 +67,27 @@ exports.up = (path, onStatus) ->
 			docker.getImage(service.image).inspectAsync()
 			.catch ->
 				dockerUtils.pullAndProtectImage(service.image, reportStatus)
+			.then ->
+				validateServiceOptions(service)
+			.then ->
+				services[serviceName].volumes = utils.defaultBinds(composeDataPath(appId, serviceName))
+		.then ->
+			writeComposeFile(composeSpec, dstPath)
 	.then ->
-		runComposeCommand(['up', '-d'], path, onStatus)
+		runComposeCommand(['up', '-d'], appId, reportStatus)
+	.catch (err) ->
+		msg = err?.message or err
+		reportStatus(error: msg)
+		throw err
 
 # Runs docker-compose down using the compose YAML at "path".
 # Reports status and errors in JSON to the onStatus function.
-exports.down = _.partial(runComposeCommand, 'down')
+exports.down = (appId, onStatus)
+	onStatus ?= console.log.bind(console)
+	reportStatus = (status) ->
+		try onStatus(status)
+	runComposeCommand([ 'down' ], appId, reportStatus)
+	.catch (err) ->
+		msg = err?.message or err
+		reportStatus(error: msg)
+		throw err
diff --git a/src/utils.coffee b/src/utils.coffee
index 13ec0138..d61e7eb1 100644
--- a/src/utils.coffee
+++ b/src/utils.coffee
@@ -223,3 +223,47 @@ exports.defaultBinds = (dataPath) ->
 		'/etc/resolv.conf:/etc/resolv.conf:rw'
 		'/var/lib/connman:/host/var/lib/connman'
 	]
+
+exports.validComposeOptions = [
+	'command'
+	'entrypoint'
+	'env_file'
+	'environment'
+	'expose'
+	'image'
+	'labels'
+	'ports'
+	'stop_signal'
+	'volumes'
+	'user'
+	'working_dir'
+	'cap_add'
+	'cap_drop'
+	'devices'
+	'dns'
+	'dns_search'
+	'tmpfs'
+	'extra_hosts'
+	'links'
+	'net'
+	'network_mode'
+	'ulimits'
+	'volumes_from'
+	'cpu_shares'
+	'cpu_quota'
+	'cpuset'
+	'domainname'
+	'hostname'
+	'mac_address'
+	'mem_limit'
+	'memswap_limit'
+	'privileged'
+	'tty'
+	'read_only'
+	'shm_size'
+	'ipc'
+	'restart'
+	'security_opt'
+	'networks'
+	'pid'
+]