CakeDC Blog


How to push Docker image to Container Registry and create App on DigitalOcean Platform Apps

The title speaks for itself, let’s jump right in!

As a preliminary step, we start from a user registered in DigitalOcean with a validated account.

Use the doctl tool for the entire communication process with DigitalOcean.

Step 1: Install doctl

$ cd ~
$ wget
$ tar xvf doctl-1.71.0-linux-amd64.tar.gz
$ sudo mv doctl /usr/local/bin


Step 2: Create an API token


Go to

Generate new token for read and write and save apart the value of token generated

<TOKEN NAME>: personaltoken 
<TOKEN VALUE>: 6e981fc2a674dbb7a610b9b85d0c8b00

Step 3: Use the API token to grant account access to doctl

$ doctl auth init --context personaltoken
Validating token... OK

Prompt for <TOKEN VALUE>, then enter it and press return 

Step 4: Validate that doctl is working

$ doctl auth init 

Validating token... OK


Prompt for <TOKEN VALUE>, then enter it and press return 

Validate by obtaining the account information

$ doctl account get

Email      Droplet Limit    Email Verified    UUID         Status

[email protected]    10     true     5415bbf8-d501-4096-9b75-ab781c017948    active


Step 5: Create a Container Registry with doctl

<MY-REGISTRY-NAME> : container-nyc-795

<REGION> : nyc3

$ doctl registry create container-nyc-795 --region nyc3

Name       Endpoint        Region slug

container-nyc-795    nyc3

Important: the region of the Container registry and Kubernetes cluster MUST be the same

Keep in mind that container names must be unique, must be lowercase, and only accepts alphanumeric characters and hyphens.


Step 6: Login to authenticate docker with your registry

$ doctl registry login
Logging Docker in to


Step 7: Create kubernetes cluster

$ doctl kubernetes cluster create cluster-static-example --region nyc3

Notice: Cluster is provisioning, waiting for cluster to be running


Notice: Cluster created, fetching credentials

Notice: Adding cluster credentials to kubeconfig file found in "/home/andres/.kube/config"

Notice: Setting current-context to do-nyc3-cluster-static-example

ID                                      Name                      Region    Version        Auto Upgrade    Status     Node Pools

d24f180b-6007-4dbc-a2fe-3952801570aa    cluster-static-example    nyc3      1.22.7-do.0    false           running    cluster-static-example-default-pool

Important: the region of the Container registry and Kubernetes cluster MUST be the same here as well. 

This operation isn’t a fast process.


Step 8: Integrate kubernetes cluster in Container register

$ doctl kubernetes cluster registry add cluster-static-example


Step 9: Get token certificate and connect to cluster

$ doctl kubernetes cluster kubeconfig save cluster-static-example

Notice: Adding cluster credentials to kubeconfig file found in "/home/andres/.kube/config"

Notice: Setting current-context to do-nyc3-cluster-static-example

To validate this, use the kubectl tool to get context. If is not installed, get the last version you find - for example in googleapis

$ wget
$ chmod +x kubectl
$ sudo mv kubectl /usr/local/bin/

Check with

$ kubectl config current-context

Here, you see: bash prompt do-ny3-cluster-sttic-example. That is the context you created in Step 7

Step 10: Generate docker image, tag and push to DigitalOcean

We assume that the user already has docker installed

$ mkdir myapp
$ mkdir myapp/html
$ nano myapp/Dockerfile


Inside Dockerfile put 


FROM nginx:latest
COPY ./html/hello.html /usr/share/nginx/html/hello.htm


We create simple Dockerfile with NGINX Server and copy the file hello.html in the default html of nginx


$ nano myapp/html/hello.html


Inside hello.html put


<!DOCTYPE html>



   <title>Hello World!</title>



   <p>This is an example of a simple HTML page served from the Nginx container.</p>



Then build a docker image file tag with repository and push with docker

$ cd myapp
$ docker build -t .

Sending build context to Docker daemon  3.584kB

Step 1/2 : FROM nginx:latest

latest: Pulling from library/nginx

c229119241af: Pull complete

2215908dc0a2: Pull complete

08c3cb2073f1: Pull complete

18f38162c0ce: Pull complete

10e2168f148a: Pull complete

c4ffe9532b5f: Pull complete

Digest: sha256:2275af0f20d71b293916f1958f8497f987b8d8fd8113df54635f2a5915002bf1

Status: Downloaded newer image for nginx:latest

 ---> 12766a6745ee

Step 2/2 : COPY ./html/hello.html /usr/share/nginx/html/hello.html

 ---> 2b9be913c377

Successfully built 2b9be913c377

Successfully tagged


$ docker images

REPOSITORY                                               TAG       IMAGE ID       CREATED         SIZE   latest    2b9be913c377   2 minutes ago   142MB


$ docker push

Using default tag: latest

The push refers to repository []

ac03ae036a53: Pushed

ea4bc0cd4a93: Pushed

fac199a5a1a5: Pushed

5c77d760e1f4: Pushed

33cf1b723f65: Pushed

ea207a4854e7: Pushed

608f3a074261: Pushed

latest: digest: sha256:22615ad4c324ca5dc13fe2c3e1d2d801bd166165e3809f96ed6a96a2b2ca2748 size: 1777

Step 11: Create app

Create file example-static-app.yaml and insert:

- rule: DEPLOYMENT_FAILED               
- rule: DOMAIN_FAILED                   
name: example-static-app                
region: nyc                             
- http_port: 80                         
    registry_type: DOCR                 
    repository: static-app              
    tag: latest                         
  instance_count: 2                     
  instance_size_slug: professional-xs   
  name: static-service                  
  - path: /                             
  source_dir: /             

Validate file with

$  doctl apps spec validate example-static-app.yaml

Create app

$ doctl apps create --spec example-static-app.yaml
Notice: App created
ID        Spec Name             Default Ingress    Active Deployment ID    In Progress Deployment ID    Created At     Updated At
55a7cb68-65b7-4ff1-b6af-388cdb1df507    example-static-app   2022-03-30 09:34:01.288257225 +0000 UTC    2022-03-30 09:34:01.288257225 +0000 UTC

If you access

And in the live url + /hello.html you can see:

Step 12: Deploy app

If you modify the code of your app, you need to generate a new image with docker and push  (see step 10). Then you don’t need to create a new app, you need to deploy the image in the already created app, with the id executing the command to deploy in bash.

$  doctl apps create-deployment 55a7cb68-65b7-4ff1-b6af-388cdb1df507


How to generate deploy of Docker image to Container Registry on DigitalOcean Platform Apps

Once you have created the application (and if you have the code in gitlab), you can create a direct deployment of your code in the DigitalOcean container and deploy on top of your application.

Step 1: Define variables

First two variables are defined in gitlab. You can find these inside project in the left menu - enter in Settings > CI/CD > Variables

$DIGITALOCEAN_API_KEY = token generated in DigitalOcean dashboard

$APP_ID = previously generated application identifier


More can be defined as the name of the repository. The value of these variables will be injected into the file that we will create below in the Step 3



Step 2: Register runner in gitlab

In your project in the left menu, go to Settings > CI/CD > Runners

Create a specific runner for the project in the URL with registration token. Register GitLab Runner from the command line. It is important to use docker and privileged

# Download the binary for your system
sudo curl -L --output /usr/local/bin/gitlab-runner

# Give it permission to execute
sudo chmod +x /usr/local/bin/gitlab-runner

# Register runner
sudo gitlab-runner register -n --url --registration-token GR1348941gx7sgV3pZFQgRqg5qUR_ --executor docker --description "My Docker Runner" --docker-image "docker:19.03.12" --docker-privileged --docker-volumes "/certs/client"


Step 3: Create file gitlab-ci.yml 

The gitlab-ci.yml file takes care of

  • Authentication and identification using doctl in DigitalOcean

  • Generating and sending the docker image to the DigitalOcean container

  • Deploying the container image to an existing app

Create the file gitlab-ci.yml in the root of your project as

image: docker:20-dind

  DOCKER_HOST: tcp://docker:2375
  DOCKER_DRIVER: overlay2
  CONTAINER_NAME: static-app

  - name: docker:20-dind
    alias: docker
    command: ["--tls=false"]

  - apk update
  - apk upgrade
  - apk add doctl --repository=
  - docker info

  stage: build
    - doctl auth init --access-token $DIGITALOCEAN_API_KEY
    - doctl account get     
    - echo $DIGITALOCEAN_API_KEY | docker login -u $DIGITALOCEAN_API_KEY --password-stdin
    - docker build -t $REPOSITORY_URL:latest .
    - docker push $REPOSITORY_URL
    - doctl apps create-deployment $APP_ID

Now, every time you do a git push in your project, the runner will automatically inject the variables defined previously in the gitlab-ci.yml file. Then, it will generate a docker image with docker (docker-in-docker) to create an image of your project, send it to the digitalocean repository and deploy it in the app configured.


That’s it!


Latest articles

Build a Single Page Application Using CakePHP and InertiaJS

Build a Single Page Application using CakePHP and InertiaJS

  The Inertia Plugin allows a CakePHP application to integrate Vue 3 components in the front end, without the need to write a specific API for data transfer. This is done  by adding a Middleware and view classes that facilitate the conversion of objects and data in JSON almost automatically, as well as the direct load in the components. The plugin is thought of as a base to extend and use your app’s specific controllers and views from. Just because  it works out of the box doesn't mean it is intended to be used exactly as is,  but this will  provide you a good kick start. See the repo here:


  • CakePHP 4.5
  • PHP >= 8.1
  • NodeJS 18.9 (only for build Vue Components, not required on running site)


Step 1: Create a basic CakePHP install

  For this example I will use a basic installation using Docker and Composer.  First you must create project from cakephp/app  
$> composer create-project --prefer-dist cakephp/app:~4.5 inertia_app $> cd inertia_app $> cp config/app_local.example.php config/app_local.php
  Then write an docker-compose.yml file as:
version: '3' services:   psql13:     image: postgres:13     container_name: inertia-app-postgres13     volumes:       - ./tmp/data/inertia-postgres13__db:/var/lib/postgresql:delegated     environment:       - POSTGRES_USER=my_app       - POSTGRES_PASSWORD=secret       - POSTGRES_DB=my_app       - PGUSER=my_app       - PGDATABASE=my_app       - PGPASSWORD=secret     ports:       - '7432:5432'     cakephp:     image: webdevops/php-nginx:8.1     container_name: inertia-app-cakephp     working_dir: /application     volumes:       - ./:/application:cached       - ~/.ssh:/home/application/.ssh:ro     environment:       - WEB_DOCUMENT_ROOT=/application/webroot       - DATABASE_URL=postgres://my_app:secret@inertia-app-postgres13:5432/my_app     ports:       - "9099:80"
  Launch the container and go to http://localhost:9099/  
$> docker-compose up -d

Step 2: Add CakePHP Inertia plugin

  Install plugin via command line:
$> composer require cakedc/cakephp-inertia
  Once installed enable it in src/Application.php, adding at the bottom of bootstrap function:
  or by command line:
$> bin/cake plugin load CakeDC/Inertia


Step 3: Create Vue App and install it

  To create Vue App type in command line:
$> bin/cake create_vue_app
  This command create in the resources directory the files that use our App, also create in root directory the files:
  • webpack.mix.js
  • package.json
  Then in root directory install with NPM:
$> npm install


Step 4: Create simple SPA (Single Page Application)

  Create a single page called dashboard that show values sets in a controller action We need to first add InertiaResponseTrait  
use CakeDC\Inertia\Traits\InertiaResponseTrait;   class PagesController extends AppController {    use InertiaResponseTrait;    ...  ...   }
  Create a new function that would look like this:
public function dashboard() {   //set default php layout of plugin that use vue   $this->viewBuilder()->setTheme('CakeDC/Inertia');     $page = [       'text' => 'hello world 1',       'other' => 'hello world 2',   ];   $this->set(compact('page')); }
  in config/routes.php uncomment lines to catch all routes:
$builder->connect('/{controller}', ['action' => 'index']); $builder->connect('/{controller}/{action}/*', []);
and comment line:
$builder->connect('/pages/*', 'Pages::display');
  Then create file resources/js/Components/Pages/Dashboard.vue that would look like this:
<script setup> import Layout from '../Layout' import { Head } from '@inertiajs/vue3' import {onMounted} from "vue";   defineProps({     csrfToken: String,     flash: Array,     page: Array, })     onMounted(() => {     console.log('Component Dashboard onMounted hook called') }) </script>   <template>     <Layout>         <Head title="Welcome" />         <h1>Welcome</h1>         <p>{{page.text}}</p>         <p>{{page.other}}</p>     </Layout> </template>
  On root directory execute:
$> npm run dev
  IMPORTANT: Whenever you modify the .vue templates, you must run this script. Go to http://localhost:9099/pages/dashboard to see that Dashboard Vue Component prints values assignments on Dashboard CakePHP function.


Step 5: Bake CRUD system

  For this example, we use sql file on config/sql/example/postgresql.pgsql   That creates a database with the relations     Once the database has been created, bake models and controllers as normal using:
$> bin/cake bake model Pages --theme CakeDC/Inertia $> bin/cake bake controller Pages --theme CakeDC/Inertia $> bin/cake bake model Tags --theme CakeDC/Inertia $> bin/cake bake controller Tags --theme CakeDC/Inertia $> bin/cake bake model Categories --theme CakeDC/Inertia $> bin/cake bake controller Categories --theme CakeDC/Inertia
  and bake templates using vue_template instead of template as:
$> bin/cake bake vue_template Pages --theme CakeDC/Inertia $> bin/cake bake vue_template Tags --theme CakeDC/Inertia $> bin/cake bake vue_template Categories --theme CakeDC/Inertia
  Again run:
$> npm run dev
  You can the results from this example by going to http://localhost:9099/pages/index   In the following recording you can see how to add, edit and delete a record without reloading the page at any time.


Step 6: Using prefix and adding a navigation menu

  Add route to prefix Admin on config/routes.php
$builder->prefix('admin', function (RouteBuilder $builder) {    $builder->fallbacks(DashedRoute::class); });
  To generate controllers and template with a prefix use --prefix option of bake command as:
$> bin/cake bake controller Pages --prefix Admin --theme CakeDC/Inertia $> bin/cake bake controller Tags --prefix Admin --theme CakeDC/Inertia $> bin/cake bake controller Categories --prefix Admin --theme CakeDC/Inertia $> bin/cake bake vue_template Pages --prefix Admin --theme CakeDC/Inertia $> bin/cake bake vue_template Tags --prefix Admin --theme CakeDC/Inertia $> bin/cake bake vue_template Categories --prefix Admin --theme CakeDC/Inertia
  You can add a horizontal menu to navigate through controllers   Edit resources/Components/Layout.vue and put inside header tag links as:
<header>    <Link as="button" href="/pages/index" class="button shadow radius right small">Pages</Link>    <Link as="button" href="/tags/index" class="button shadow radius right small">Tags</Link>    <Link as="button" href="/categories/index" class="button shadow radius right small">Categories</Link> </header>
  Again run:
$> npm run dev
  You can see the results from this  example by going to http://localhost:9099/admin/pages/index   In the following recording you can see how to add, edit and delete a record without reloading the page at any time and navigate through pages, tags and categories.

  Hopefully this example will make your experience easier! Let us know: [email protected].

When and why should you upgrade to CakePHP 5?

CakePHP 5.0.0 was released on September 10th. The current version as of today is 5.0.3 (released Nov 28th and compatible with PHP 8.3 You might be asking yourself some questions related to the upgrade… here's what we've been recommending to our clients to do since version 5 was released. Leaving aside the obvious reasons for an upgrade, today we're going to categorize the decision from 2 different points of view: Your current CakePHP version, and your role in the project.

When should you upgrade? 

  We are going to use current CakePHP version as the main criteria: * If you are in CakePHP <= 2   * We strongly recommend an upgrade as soon as possible. If you are unable to upgrade, try to keep your PHP version and all the underlying dependencies as fresh as you can and isolate the application as much as possible. If your application is internal, consider using a VPN blocking all outside traffic. If your site is open to the public, consider using an isolated environment, hardened. Adding a web application firewall and a strict set of rules could also help to mitigate potential security issues. Even if CakePHP is very secure, the older versions of CakePHP, like  1 and 2  have a very old code base , and other vendors/ libraries could be a serious security risk for your project at this point.   * If you are in CakePHP 3.x   * The effort to upgrade at least to CakePHP 4.x should not be a blocker. We would recommend upgrading at least to the latest CakePHP 4.5.x. You can actually "ignore" the deprecations for now, you don't need to plan for upgrading your authentication/authorization layers just yet, focus on getting your project stable and up to CakePHP 4.5.x in the first round.   * If you are in CakePHP 4.x   * Upgrading to CakePHP 5.x is not an immediate priority for you.   * I would say, 2024 is a good time to start planning for an upgrade. Feature and bugfix releases for 4.x will continue until September 2025. Security fixes will continue for 4.x until September 2026. You have plenty of time to consider an upgrade, and take advantage of newer (and faster!) PHP versions.  

Why should you upgrade? 

  We are going to use your role in the project to provide some good reasons: * If you are a developer   * More strict types, meaning better IDE support and more errors catched at development time.   * New features in CakePHP 5.x will make your code more readable, like Typed finder parameters      * Quality of life features, reducing development time like   * Compatibility with PHP 8.3 for extra performance & support   * If you are a manager   * Ensure your development team is forced to drop old auth code and embrace the new authentication/authorization layer   * The new authentication layer will allow you to easily integrate features like single sign on, two factor authentication or hardware keys (like Yubikeys), as there are plugins available handling all these features.   * Get an extended support window. CakePHP is one of the longest maintained frameworks out there, upgrading to CakePHP 5 will keep your core maintained past 2026.   * Upgrade to PHP 8.3 and force legacy vendors to be up to date with the new version, this will also push your team to get familiar with the new PHP core features.   * If you are an investor, not directly related with the project day-to-day operations   * Secure your inversion for a longer period.   * Reduce your exposure to security issues.   * Send a strong message to your partners, keeping your product updated with the latest technology trends.   * Send a strong message to your team, investing in the upgrade of your application will let them know the project is aiming for a long term future.   In conclusion, upgrading to CakePHP 5 is a good move for 2024 whether you're a developer, manager, or investor. The version 5 is stable and ready to go. Staying current becomes not just a best practice but a strategic advantage.   If you are in doubt, feel free to contact us. We'll review your case (for free) and provide an actionable recommendation based on your current situation in the next business day.  

A quick CakePHP Local environment with DDEV

In the realm of web development, a seamless local environment is the bedrock for efficient and stress-free coding. Enter DDEV, a powerful tool that simplifies the setup process and empowers developers to dive into their projects with ease. In this blog post, we'll embark on a journey to demystify the process of setting up a local development environment using DDEV. Whether you're a seasoned developer or just starting in the world of web development, optimizing your local environment can significantly enhance your workflow.

Pre Conditions :

Install Docker and install DDEV

Step 1: Create a new CakePHP project skeleton 

composer create-project cakephp/app myproject A new folder "myproject" will be created with a CakePHP project skeleton inside. Go to this new directory and proceed with the following instructions.

Step 2: Initial ddev setup

Run ddev config
This will do the initial ddev setup, press enter for all questions.  Run ddev auth ssh
This will add ssh key authentication to the ddev-ssh-auth container

Step 3: Adjust the settings

Inside "myproject" a new .ddev folder will be created, open config.yaml  and adjust there: php version, database and the database url environment.  For PHP:
php_version: "8.1"

For the database: database: type: mysql version: "8.0" For the environment variable: web_environment: - DATABASE_URL=mysql://db:db@db/db

Step 4: Start ddev

ddev start  This will spin up the project.

Step 5: Open your application

ddev launch This will open your project in a browser.   Once you have the application up and running, some useful commands you could run are:
  • ddev composer to execure composer
  • ddev mysql to get into the database
  • ddev ssh takes you into the web container.
In this link you can see more useful commands.   Hope you enjoy playing with DDEV!





We Bake with CakePHP