Getting started

Projects and services

A project is a group of services that run together on one private network. A service is an app or a database. There are no environments: for staging, make a second project.

Projects

A project is a name and a Docker network. Its name is unique across the whole instance, and every service in it joins the network shed-<projectID>. Services in the same project reach each other by service name; services in different projects can't see each other.

Deleting a project tears down everything under it: containers, volumes, images, build logs, and finally the network.

Projectunique nameServicename = DNS labelDeploymentshistory · one activeContainershed-<svc>-<dep>VariablesKEY=value, ${{ }} refsEnvironmentat deploy timeDomainscustom or generatedCaddy routehost → IP:portVolumessurvive deploysDocker volumeshed-vol-<id>Databases are services too: a template supplies the image, port, a volume, and generated variables.
The data model. Solid arrows are ownership; dashed arrows are what shed creates from it.

Services

A service is one workload. Its name must be a DNS label (lowercase letters, digits, and hyphens, at most 63 characters), is unique within its project, and can't be changed after creation, because it is also the service's private hostname. Creating a service starts its first deployment immediately, with the trigger create.

SettingMeaning and default
repo, branchAn app built from a GitHub repository. Pushes to the branch deploy it.
rootDirSubdirectory of the repo used as the build context. Empty means the root.
dockerfilePathDockerfile relative to rootDir. Empty means auto-detect.
imageAn app run from a Docker image instead, or a database's image.
portContainer port. New repo apps start at 8080 and receive it as PORT.
startCommandOverrides the image's command.
healthcheckPathMust start with /. Empty means a TCP connect to the port.
publicPortPublishes a host TCP port. Needs a container port.
cpuLimit, memoryLimit1 core and 1 GiB by default. 0 is unlimited; memory must be at least 64 MiB.
autoDeployOn by default. Deploy on every push to the branch.
waitForCiOff by default. Hold each deployment until the commit's CI passes.

Reaching a service from another one is a matter of its name and port, such as postgres:5432. The private network page covers how that name moves between containers during a deploy.

Service kinds

There are five kinds. An app is yours: shed builds it from a repository or pulls an image. The four databases come from built-in templates, which fix the image, port, and data directory, and generate the variables an app needs to connect.

KindImagePortVolume atVariables created
postgrespostgres:18-alpine5432/var/lib/postgresqlPOSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB, DATABASE_URL
mysqlmysql:93306/var/lib/mysqlMYSQL_ROOT_PASSWORD, MYSQL_DATABASE, MYSQL_URL, DATABASE_URL
mongomongo:827017/data/dbMONGO_INITDB_ROOT_USERNAME, MONGO_INITDB_ROOT_PASSWORD, MONGO_URL
redisredis:8-alpine6379/dataREDIS_PASSWORD, REDIS_URL

Passwords are 24 random alphanumeric characters, so they are safe to embed in URLs. Redis starts with --requirepass and --appendonly yes. The Postgres volume covers /var/lib/postgresql, not the data directory itself, because Postgres 18 images keep data in a version-specific subdirectory.

Service status

A service's status isn't stored. Every time you ask, shed derives it from the service's newest deployment, its stopped flag, and whether the active deployment's container is running in Docker. The rules are checked top to bottom and the first match wins.

1 · Latest deployment is in progressqueued, waiting, building or deployingDeployingyesno2 · The service is stoppedyou pressed Stop and nothing has started it sinceStoppedyesno3 · Active deployment, container runningDocker says the container is upOnlineyesno4 · Active deployment, container not runningit exited, or was removed behind shed's backCrashedyesno5 · No active deployment, latest one failedthe first deploy never went liveFailedyesno6 · Anything elseno deployments yet, or the rest were canceled or skippedOfflinealways
The first rule whose condition holds decides the status.

Two consequences are worth knowing. A failed redeploy doesn't change the status of a service that still has an active deployment, because the old container keeps serving: it stays active. And crashed is detected by the container's absence, not by an event, so it can appear on any listing after Docker reports the container isn't running.

Try the rules
Active deployment
StatusFailed
  1. Latest deployment is in progress
  2. The service is stopped
  3. Active deployment, container running
  4. Active deployment, container not running
  5. No active deployment, latest one failed
  6. Anything else

A restore fence doesn't change status. It blocks deploys, starts, and restarts of the service until its restore finishes; see restore fences.

When settings apply

Settings are saved immediately, but a running container was created from the settings that existed when it started. Most settings reach it with the next deployment. A few take effect right away because shed reads them at the moment they matter.

SettingTakes effect
DomainsImmediately. shed reloads the proxy routes when you add or remove one.
autoDeploy, branch, repoImmediately for the next push: the webhook reads them from the database.
waitForCiAt the start of the next deployment.
VariablesNext deployment. A container's environment is fixed when it is created.
port, publicPort, startCommand, healthcheckPathNext deployment. Until then routes and a recreated container keep the active deployment's recorded port.
cpuLimit, memoryLimitNext deployment, when the container is created.
VolumesNext deployment. A new mount is added to the next container.
rootDir, dockerfilePath, imageNext build or pull.

Restart doesn't re-read settings, since it restarts the existing container. Start recreates the container only if it has gone missing, and then uses the current variables with the recorded port. To apply a change, deploy; see the pipeline.