> ## Documentation Index
> Fetch the complete documentation index at: https://developer.upsun.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Up(sun) and Running with Twill

export const PostMeta = ({data = {}}) => {
  const {author, date, image} = data;
  const authors = Array.isArray(author) ? author : author ? [author] : [];
  const resolveAuthor = slug => {
    const entry = AUTHOR_MAP[slug] || ({});
    const name = entry.name || slug;
    const github = entry.github || null;
    const linkedin = entry.linkedin || null;
    const url = github ? `https://github.com/${github}` : linkedin || null;
    const avatarUrl = github ? `https://github.com/${github}.png?size=64` : null;
    return {
      name,
      url,
      avatarUrl
    };
  };
  const formattedDate = date ? new Date(date).toLocaleDateString('en-US', {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
  }) : null;
  if (!image && authors.length === 0 && !formattedDate) return null;
  const AUTHOR_MAP = {
    "aaron-collier": {
      "name": "Aaron Collier"
    },
    "aaron-dudenhofer": {
      "name": "Aaron Dudenhofer"
    },
    "aaron-porter": {
      "name": "Aaron Porter"
    },
    "adriaan-odendaal": {
      "name": "Adriaan Odendaal"
    },
    "ajmal": {
      "name": "Ajmal Siddiqui"
    },
    "akalipetis": {
      "name": "Antonis Kalipetis"
    },
    "alexander-varwijk": {
      "name": "Alexander Varwijk"
    },
    "alicia-bevilacqua": {
      "name": "Alicia Bevilacqua"
    },
    "amelie-deguerry": {
      "name": "Amelie Deguerry"
    },
    "anacidre": {
      "name": "Ana Cidre",
      "linkedin": "https://www.linkedin.com/in/ana-cidre"
    },
    "andoni": {
      "name": "Andoni Auzmendi"
    },
    "andrei-taranu": {
      "name": "Andrei (Alex) Taranu",
      "linkedin": "https://www.linkedin.com/in/andrei-alex-taranu/"
    },
    "andrew-baxter": {
      "name": "Andrew Baxter"
    },
    "andrew-melck": {
      "name": "Andrew Melck"
    },
    "antoine-crochet-damais": {
      "name": "Antoine Crochet Damais"
    },
    "augustin-delaporte": {
      "name": "Augustin Delaporte",
      "linkedin": "https://www.linkedin.com/in/augustindelaporte/"
    },
    "branislav-bujisic": {
      "name": "Branislav Bujisic"
    },
    "carl-smith": {
      "name": "Carl Smith"
    },
    "caroline-leroy": {
      "name": "Caroline Leroy"
    },
    "cati-mayer": {
      "name": "Cati Mayer"
    },
    "catplat": {
      "name": "C Trinkwon"
    },
    "ceelolulu": {
      "name": "Celeste van der Watt"
    },
    "chadwcarlson": {
      "name": "Chad Carlson",
      "github": "chadwcarlson",
      "linkedin": "https://www.linkedin.com/in/chadwcarlson"
    },
    "chris-ward": {
      "name": "Chris Ward"
    },
    "chris-yates": {
      "name": "Chris Yates"
    },
    "christian-sieber": {
      "name": "Christian Sieber"
    },
    "christopher-lockheardt": {
      "name": "Christopher Lockheardt"
    },
    "christopher-skene": {
      "name": "Christopher Skene"
    },
    "chuck-morgan": {
      "name": "Chuck Morgan"
    },
    "corey-dockendorf": {
      "name": "Corey Dockendorf"
    },
    "crell": {
      "name": "Crell"
    },
    "damz": {
      "name": "Damz"
    },
    "dan-morrison": {
      "name": "Dan Morrison"
    },
    "davidbonachera": {
      "name": "David Bonachera",
      "github": "davidbonachera",
      "linkedin": "https://www.linkedin.com/in/davidbonachera"
    },
    "dereliahmet1": {
      "name": "Ahmet Faruk Dereli"
    },
    "devicezero": {
      "name": "Jonas Kröger",
      "github": "devicezero",
      "linkedin": "https://www.linkedin.com/in/jonaskroeger/"
    },
    "doug-goldberg": {
      "name": "Doug Goldberg"
    },
    "duncan-naves": {
      "name": "Duncan Naves",
      "github": "duncannaves",
      "linkedin": "https://www.linkedin.com/in/duncan-naves-a94423aa"
    },
    "erika-bustamante": {
      "name": "Erika Bustamante"
    },
    "fabpot": {
      "name": "Fabien Potencier"
    },
    "flovntp": {
      "name": "Florent Huck",
      "github": "flovntp",
      "linkedin": "https://www.linkedin.com/in/florenthuck"
    },
    "fred-plais": {
      "name": "Fred Plais"
    },
    "gauthier-garnier": {
      "name": "Gauthier Garnier"
    },
    "gilzow": {
      "name": "Paul Gilzow"
    },
    "gmoigneu": {
      "name": "Guillaume Moigneu",
      "github": "gmoigneu",
      "linkedin": "https://www.linkedin.com/in/guillaumemoigneu/"
    },
    "gregqualls": {
      "name": "Greg Qualls"
    },
    "guguss": {
      "name": "Augustin Delaporte"
    },
    "haylee-millar": {
      "name": "Haylee Millar"
    },
    "ivana-kotur": {
      "name": "Ivana Kotur"
    },
    "jackrabbithanna": {
      "name": "Mark Hanna"
    },
    "jared-wright": {
      "name": "Jared Wright",
      "github": "jww-sh",
      "linkedin": "https://www.linkedin.com/in/jaredwaynewright"
    },
    "jessica-orozco": {
      "name": "Jessica Orozco"
    },
    "joey-stanford": {
      "name": "Joey Stanford"
    },
    "john-grubb": {
      "name": "John Grubb"
    },
    "jonas-kruger": {
      "name": "Jonas Kruger"
    },
    "kathryn-frazer": {
      "name": "Kathryn Frazer"
    },
    "kemiojo": {
      "name": "Kemi Elizabeth Ojogbede"
    },
    "kieronsambrook-smith": {
      "name": "Kieronsambrook Smith"
    },
    "laurent-arnoud": {
      "name": "Laurent Arnoud",
      "linkedin": "https://www.linkedin.com/in/laurent-arnoud-861b44121/"
    },
    "letoya-boyne": {
      "name": "Letoya Boyne"
    },
    "lolautruche": {
      "name": "Jérôme Vieilledent"
    },
    "lyly-lepinay": {
      "name": "Lyly Lepinay"
    },
    "manauwar-alam": {
      "name": "Manauwar Alam"
    },
    "marc-antoine-porri": {
      "name": "Marc Antoine Porri"
    },
    "maria-antinkaapo": {
      "name": "Maria Antinkaapo"
    },
    "maria-de-anton": {
      "name": "Maria De Anton"
    },
    "mark-dorison": {
      "name": "Mark Dorison"
    },
    "markus-hausammann": {
      "name": "Markus Hausammann"
    },
    "mary-thomas": {
      "name": "Mary Thomas"
    },
    "mathias-bolt-lesniak": {
      "name": "Mathias Bolt Lesniak"
    },
    "mathieu-strauch": {
      "name": "Mathieu Strauch"
    },
    "matthias-van-woensel": {
      "name": "Matthias Van Woensel",
      "linkedin": "https://www.linkedin.com/in/matthias-van-woensel-267a069"
    },
    "michael-sharp": {
      "name": "Michael Sharp"
    },
    "mupsi": {
      "name": "Marine Gandy"
    },
    "natalie-harper": {
      "name": "Natalie Harper"
    },
    "ngommenginger": {
      "name": "Nicolas Gommenginger",
      "linkedin": "https://www.linkedin.com/in/nicolas-gommenginger"
    },
    "nicholas-bennison": {
      "name": "Nicholas Bennison"
    },
    "nicholas-vahalik": {
      "name": "Nicholas Vahalik"
    },
    "nick-hardiman": {
      "name": "Nick Hardiman"
    },
    "nickanderegg": {
      "name": "Nickanderegg"
    },
    "nicolas-grekas": {
      "name": "Nicolas Grekas",
      "github": "nicolas-grekas",
      "linkedin": "https://www.linkedin.com/in/nicolasgrekas/"
    },
    "niti-malwade": {
      "name": "Niti Malwade"
    },
    "opensocialteam": {
      "name": "Opensocialteam"
    },
    "ori-pekelman": {
      "name": "Ori Pekelman"
    },
    "otavio-santana": {
      "name": "Otavio Santana"
    },
    "palwandi": {
      "name": "Pawan Alwandi",
      "github": "pawpy",
      "linkedin": "https://www.linkedin.com/in/pawanalwandi"
    },
    "patrick-boest": {
      "name": "Patrick Boest"
    },
    "patrick-dawkins": {
      "name": "Patrick Dawkins",
      "github": "pjcdawkins",
      "linkedin": "https://www.linkedin.com/in/patrickdawkins"
    },
    "patrick-klima": {
      "name": "Patrick Klima"
    },
    "pjcdawkins": {
      "name": "Pjcdawkins"
    },
    "prineet-kaurbhurji": {
      "name": "Prineet Kaurbhurji"
    },
    "quentin-sinig": {
      "name": "Quentin Sinig"
    },
    "ralt": {
      "name": "Florian Margaine",
      "github": "ralt",
      "linkedin": "https://www.linkedin.com/in/florian-margaine-43971136"
    },
    "ramanathanramakrishnamurthy": {
      "name": "Ramanathanramakrishnamurthy"
    },
    "remi-lejeune": {
      "name": "Rémi Lejeune"
    },
    "ribel": {
      "name": "Taras Kruts"
    },
    "robert-douglass": {
      "name": "Robert Douglass"
    },
    "rudy-weber": {
      "name": "Rudy Weber"
    },
    "ryan-hicks": {
      "name": "Ryan Hicks"
    },
    "sabri-helal": {
      "name": "Sabri Helal"
    },
    "savannah-bergeron": {
      "name": "Savannah Bergeron"
    },
    "shannon-vettes": {
      "name": "Shannon Vettes"
    },
    "shawn-ogasawara": {
      "name": "Shawn Ogasawara",
      "linkedin": "https://www.linkedin.com/in/shawn-ogasawara-83a9a0/"
    },
    "shawna-spoor": {
      "name": "Shawna Spoor"
    },
    "shedrack-akintayo": {
      "name": "Shedrack Akintayo"
    },
    "simon-ruggier": {
      "name": "Simon Ruggier"
    },
    "sophie-van-der-kindere": {
      "name": "Sophie Van Der Kindere"
    },
    "stefanos-thampis": {
      "name": "Stefanos Thampis"
    },
    "stephen-weinberg": {
      "name": "Stephen Weinberg"
    },
    "sukhman-virk": {
      "name": "Sukhman Virk"
    },
    "sumaira-nazir": {
      "name": "Sumaira Nazir"
    },
    "sumer": {
      "name": "Sümer Cip"
    },
    "syed-raza": {
      "name": "Syed Raza"
    },
    "tamara-bacchia": {
      "name": "Tamara Bacchia"
    },
    "tara-arnold": {
      "name": "Tara Arnold"
    },
    "theosakamg": {
      "name": "Mickael Gaillard",
      "github": "theosakamg"
    },
    "thomasdiluccio": {
      "name": "Thomas di Luccio"
    },
    "tim-anderson": {
      "name": "Tim Anderson"
    },
    "tom-helmer-hansen": {
      "name": "Tom Helmer Hansen"
    },
    "tylermills": {
      "name": "Tyler Mills"
    },
    "upsun": {
      "name": "Upsun"
    },
    "veronika-tolkachova": {
      "name": "Veronika Tolkachova",
      "linkedin": "https://www.linkedin.com/in/veronika-tolkachova-169167a2"
    },
    "vince-parker": {
      "name": "Vince Parker"
    },
    "vinnie-russo": {
      "name": "Vincenzo Russo"
    },
    "vrobert78": {
      "name": "Vincent Robert",
      "github": "vrobert78",
      "linkedin": "https://www.linkedin.com/in/vincent-robert-498a883"
    },
    "yuriy-babenko": {
      "name": "Yuriy Babenko"
    },
    "yuriy-gerasimov": {
      "name": "Yuriy Gerasimov"
    }
  };
  return <div className="post-meta">
      {(authors.length > 0 || formattedDate) && <div className="post-meta-info">
          {authors.length > 0 && <div className="post-meta-authors">
              {authors.map(slug => {
    const {name, url, avatarUrl} = resolveAuthor(slug);
    const inner = <>
                    {avatarUrl && <img src={avatarUrl} alt={name} className="post-meta-avatar" />}
                    <span className="post-meta-author-name">{name}</span>
                  </>;
    return url ? <a key={slug} href={url} target="_blank" rel="noopener noreferrer" className="post-meta-author">
                    {inner}
                  </a> : <span key={slug} className="post-meta-author">{inner}</span>;
  })}
            </div>}
          {authors.length > 0 && formattedDate && <span className="post-meta-separator" aria-hidden="true">·</span>}
          {formattedDate && <span className="post-meta-date">{formattedDate}</span>}
        </div>}
      {image && <img src={image} alt="" className="post-meta-image" aria-hidden="true" />}
    </div>;
};

<PostMeta data={{ author: ["gilzow"], date: "2025-03-12T10:00:00+00:00", image: "/images/posts/tutorials/deploying-twill-on-upsun/twill_cms_logo_white.webp" }} />

## Overview

This guide provides instructions for deploying and working with [Twill](https://twillcms.com/), an "open-source
CMS toolkit for Laravel" on Upsun, by Platform.sh. If you're not familiar with
Forem, it's a Laravel package that helps developers rapidly create a custom CMS that is beautiful, powerful, and
flexible.

Before we dive in, let's establish some assumptions that we’ve made about you to ensure you can follow this guide
successfully:

* You have [an account set up on Upsun.com](https://upsun.com) and the necessary permissions to create a new project
* You have the [Upsun CLI installed](https://docs.upsun.com/administration/cli.html#1-install)
* You have [authenticated in the CLI](https://docs.upsun.com/administration/cli.html#2-authenticate)
* You have the same PHP version installed locally that you want to use in your application container, and it has both
  [pdo](https://www.php.net/manual/en/pdo.installation.php) and [pdo\_mysql](https://www.php.net/manual/en/ref.pdo-mysql.php)
  extensions enabled.

## Setting up the application and repository

First, we need to start a Laravel project. From a terminal/command prompt, create a new Laravel project locally:

```bash {filename="Terminal"} theme={null}
composer create-project laravel/laravel laravel-twill && cd laravel-twill
```

Composer will create a new Laravel project for us, and then we'll `cd` into the new directory.
Once the project is created and we're in the new directory, we'll go go ahead and initialize our git repository.

From your terminal/command prompt:

```bash {filename="Terminal"} theme={null}
git init .
```

By default, git will name the initial branch `master`. I'm going to rename mine to `main` and will reference this name
throughout the remainder of this guide. To rename the branch, from your terminal/command prompt:

```bash {filename="Terminal"} theme={null}
git branch -m main
```

Next we need to add Twill as a dependency to our Laravel project. In your terminal/command prompt:

```bash {filename="Terminal"} theme={null}
composer require area17/twill:"^3.4"
```

Composer will add Twill along with all of Twill's dependencies to your project. With Twill now added, we're ready to set up
some base Upsun configuration files. In your terminal, start the project initialization:

```bash {filename="Terminal"} theme={null}
upsun project:init
```

The CLI should automatically detect that your stack is based on Laravel. It will then select PHP as your runtime, and
Composer as your dependency manager. Next, it will prompt us for the project's name (you can select the default) and
lastly, the services we need. In this case, select MariaDB and Redis.

```{filename="Terminal"} theme={null}
upsun project:init
Welcome to Upsun!
Let's get started with a few questions.

We need to know a bit more about your project. This will only take a minute!

What language is your project using? We support the following: [Ruby]

✓ Detected stack: Laravel
✓ Detected runtime: PHP
✓ Detected dependency managers: Composer
Tell us your project's application name: [laravel-twill]

                       (\_/)
We're almost done...  =(^.^)=

Last but not least, unless you're creating a static website, your project uses services. Let's define them:
Select all the services you are using:
Use arrows to move, space to select, type to filter
> [x]  MariaDB
  [ ]  MySQL
  [ ]  PostgreSQL 
  [x]  Redis
  [ ]  Redis Persistent
  [ ]  Memcached
  [ ]  OpenSearch
```

After selecting your services and hitting enter, the Upsun CLI will generate two new files for you:

* `.environment` in the root of the Forem repository and a new directory
* `.upsun` with a single file inside named `config.yaml`

Now go ahead and follow the CLI's instructions to `git add` and `git commit`.

## Leverage environment variables

While the `project:init` command correctly identified our project as being Laravel-based, and correctly set up our
Upsun configuration files for success, there are a few changes we'll need to make in order to deploy Twill.
Upsun does not read environment variable values from `.env` but does provide a way to generate environment variables
via script. The `.environment` file that the CLI generated previously runs as a script in dash when the container starts
and on all SSH logins. It can be used to set any environment variables directly, such as the PATH variable. Perfect!

<Note>
  **Note**: I'm going to walk through each group of additions to this file, but a link to the file in its entirety will also be included at the [end of this post](#all-example-files).
</Note>

To start, we need to add the `APP_KEY` and value that Laravel generated for us in the `.env` file into our
`.environment`. With your favorite IDE, open up the `.env` file and copy the line that starts with `APP_KEY`. Next, open
the `.environment` file. Scroll down to the bottom, just past `export REDIS_URL`. On a new line, type `export ` and
paste in the line we just copied. It should look similar to the following:

```shell {filename=".environment"} theme={null}
export APP_KEY=base64:TGFyYXZlbCArIFR3aW/sICsgVXBzdW4gPT0gQXdlc29tZQo=
```

Next up, we need to modify the database-related environment variables so we can use a tunnel to our database container more easily.
Open the `.environment` file again and at the top of the file, add the following:

```shell {filename=".environment"} theme={null}
export RELATIONSHIPS_JSON="$(echo $PLATFORM_RELATIONSHIPS | base64 --decode)"
```

Now, locate the variables starting with `DB_`. They should be located right between the comments:
`# Set database environment variables` and `# Set Laravel-specific environment variables`.
Once you've found those variables, replace them with the following:

```shell {filename=".environment"} theme={null}
export DB_HOST="$(echo $RELATIONSHIPS_JSON | jq -r '.mariadb[0].host')"
export DB_PORT="$(echo $RELATIONSHIPS_JSON | jq -r '.mariadb[0].port')"
export DB_PATH="$(echo $RELATIONSHIPS_JSON | jq -r '.mariadb[0].path')"
export DB_DATABASE="$DB_PATH"
export DB_USERNAME="$(echo $RELATIONSHIPS_JSON | jq -r '.mariadb[0].username')"
export DB_PASSWORD="$(echo $RELATIONSHIPS_JSON | jq -r '.mariadb[0].password')"
export DB_SCHEME="$(echo $RELATIONSHIPS_JSON | jq -r '.mariadb[0].scheme')"
```

Save the file, and commit everything to your repository.

```bash {filename="Terminal"} theme={null}
git add . && git commit -m "initial commit"
```

## Create the Upsun project

Before we can deploy our application, we'll need to create a new project on Upsun. To do so, we'll type the command
below into command line:

```bash {{filename="Terminal"}} theme={null}
upsun project:create
```

The CLI tool will now walk you through the creation of a project by asking you for:

* Your organization
* The project's title
* The region you want the application housed
* The branch name (use the same one you set earlier)
  For now, allow the CLI tool to set Upsun as your repository's remote, and then select Y for the tool to create the project. The Upsun
  bot will begin the generation of your Upsun project and once done, will report back the details of your project
  including the project's ID, and URL where you can manage the project from the Upsun web console. Don't worry if you
  forget any of this information, you can retrieve it later with:

```bash {{filename="Terminal"}} theme={null}
upsun project:info
```

You can also launch the web console for your project at any time by doing the following:

```bash {{filename="Terminal"}} theme={null}
upsun web
```

## First push

We're finally to the point where we can push our repository to Upsun and have it perform the first build:

```bash {{filename="Terminal"}} theme={null}
upsun push -y
```

This first push will take a few minutes, so take advantage of the opportunity to grab yourself something to drink.

Once the build and deploy is complete, the CLI will report back the URLs assigned to your project. While I know it's
exciting, we're not quite ready to visit our site. We have a few remaining tasks that need to be completed.

## Finish setting up Twill

While we added Twill as a dependency earlier, we still haven't installed it. To do so, we still need to run the
`twill:install` Artisan command. This will:

* Create an `admin.php` routes file in our application's routes directory. This is where we will declare our own
  admin console routes.
* Migrate the database with Twill's migrations.
* Publish Twill's configuration files to our application's config directory.
* Publish Twill's assets for the admin console UI.
* Prompt us to create a superadmin user.

However, before we can run the command, we need to make sure Twill can communicate with our database. We could use a
local development environment like [ddev](https://ddev.com/), a docker container, or
[Laravel Homestead](https://laravel.com/docs/12.x/homestead). Since this is a brand new project, we can instead
connect directly to our database container using a
[tunnel](https://docs.upsun.com/development/ssh.html#use-a-direct-tunnel)! This will expose local endpoints for our database service that allows Twill to connect to the database exactly as it
will in the application container.

From the command line:

```bash {{filename="Terminal"}} theme={null}
upsun tunnel:open
```

Make sure to follow the directions at the end and export the `PLATFORM_RELATIONSHIPS` environment variable:

```bash {{filename="Terminal"}} theme={null}
export PLATFORM_RELATIONSHIPS="$(platform tunnel:info --encode)"
```

The last thing we need to do before we run the install command is to reuse our `.environment` file to create the
environment variables in our current terminal session that Twill can utilize:

```bash {{filename="Terminal"}} theme={null}
source .environment
```

And now you can run Twill's install command:

```bash{filename="Terminal"} theme={null}
php artisan twill:install
```

Follow the prompts to create a superadmin account. Once it is finished, the TWill install will have created several new
files, and a few directories of files that we'll need to add to our repository:

* config/translatable.php
* config/twill.php
* public/.gitkeep
* public/assets/\*
* resources/views/site/\*
* routes/twill.php

Once you `git add` all the above and `git commit`, you're ready to push.

```bash{filename="Terminal"} theme={null}
git add config/translatable.php config/twill.php public/.gitkeep routes/twill.php public/assets resources/views/site
git commit -m "adds Twill post-install files"
upsun push -y 
```

Once this deployment completes, you'll be fully Up(sun) and running with Twill!

## All example files

All the example files referenced in the article above are available in our
[Upsun Snippets repository](https://github.com/upsun/snippets/tree/main/examples/twill).
