Skip to content

Commit

Permalink
Change execution to be unprivileged (#311)
Browse files Browse the repository at this point in the history
* Update PHP patch versions to 8.1.28, 8.2.18, and 8.3.6

* Removed NGINX_USER variable

* Changed FPM-NGINX to be unprivileged

* Improved readability of health check

* Improved health check

* Simplified NGINX config

* Set cert to not be a CA cert

* Configured Apache directories for changing permissions

* Changed Apache to run as unprivileged

* Converted NGINX Unit to be unprivileged

* Dropped permissions on CLI image

* Improve UX with better healthcheck notifications

* Added empty line for easier expansion by scripts

* Refactor .env.example file in docs directory

* Remove beta notice

* Add unprivileged by default support for Docker images

* Added hetzner

* Add default configurations for unprivileged Docker images

* Update Dockerfile examples in upgrade guide

* Improved syntax highlighting

* Updated versions in README

* Renamed provided PHP ini file per #242 (comment)

* Documented PHP ini customizations. Fixes #242

* Restructured the docs

* Update redirect paths in DocumentDrivenNotFound.vue

* Updated docs on Laravel Automations script

* Update Laravel task scheduler documentation and Docker Compose example

* Update Laravel task scheduler documentation and Docker Compose example

* Update links in Laravel Automations documentation

* Update Laravel task scheduler documentation and Docker Compose example

* Update Laravel queue and Laravel Horizon documentation

* Clear config cache before running migrations. Fixes #303

* Remove comment

* Added file permissions docs

* Update Docker image migration guide

* Removed old beta docs

* Added privileges

* Update command reference documentation

* Update contributing guide with link to docs contribution instructions

* Fixed links

* Update static site generation command in service_deploy-static-site.yml

* Added helpful notes to the migration guide

* Changed format of docker-php-serversideup-set-id

* Added /var/www/html/ to chown

* Update migration guide with PHP_FPM_POOL_NAME rename

* Fix SSL keypair generation in 10-init-webserver-config.sh

* Add PHP_OPCACHE_ENABLE=1 to production environment for increased performance

* Improved get-php-versions script to accept null values and rely more on the Official PHP data
  • Loading branch information
jaydrogers authored Apr 22, 2024
1 parent 469dd8d commit bcc5b18
Show file tree
Hide file tree
Showing 57 changed files with 723 additions and 178 deletions.
3 changes: 1 addition & 2 deletions .github/workflows/service_deploy-static-site.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ jobs:

- run: |
yarn install --frozen-lockfile
yarn build
npx nuxi generate
yarn generate
working-directory: ./docs
- name: Publish to Cloudflare Pages
Expand Down
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,11 @@ serversideup/php:8.2-fpm-nginx

| ⚙️ Variation | 🚀 Version |
| ------------ | ---------- |
| cli | [![serversideup/php:8.2-cli](https://img.shields.io/docker/image-size/serversideup/php/8.2-cli?label=serversideup%2Fphp%3A8.2-cli)](https://hub.docker.com/r/serversideup/php/tags?name=8.2-cli&page=1&ordering=-name)<br />[![serversideup/php:8.1-cli](https://img.shields.io/docker/image-size/serversideup/php/8.1-cli?label=serversideup%2Fphp%3A8.1-cli)](https://hub.docker.com/r/serversideup/php/tags?name=8.1-cli&page=1&ordering=-name)<br />[![serversideup/php:8.0-cli](https://img.shields.io/docker/image-size/serversideup/php/8.0-cli?label=serversideup%2Fphp%3A8.0-cli)](https://hub.docker.com/r/serversideup/php/tags?name=8.0-cli&page=1&ordering=-name)<br />[![serversideup/php:7.4-cli](https://img.shields.io/docker/image-size/serversideup/php/7.4-cli?label=serversideup%2Fphp%3A7.4-cli)](https://hub.docker.com/r/serversideup/php/tags?name=7.4-cli&page=1&ordering=-name) |
| fpm | [![serversideup/php:8.2-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm?label=serversideup%2Fphp%3A8.2-fpm)](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm&page=1&ordering=-name)<br />[![serversideup/php:8.1-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm?label=serversideup%2Fphp%3A8.1-fpm)](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm&page=1&ordering=-name)<br />[![serversideup/php:8.0-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm?label=serversideup%2Fphp%3A8.0-fpm)](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm&page=1&ordering=-name)<br />[![serversideup/php:7.4-fpm](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm?label=serversideup%2Fphp%3A7.4-fpm)](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm&page=1&ordering=-name) |
| fpm-apache | [![serversideup/php:8.2-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm-apache?label=serversideup%2Fphp%3A8.2-fpm-apache)](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm-apache&page=1&ordering=-name)<br />[![serversideup/php:8.1-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm-apache?label=serversideup%2Fphp%3A8.1-fpm-apache)](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm-apache&page=1&ordering=-name)<br />[![serversideup/php:8.0-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm-apache?label=serversideup%2Fphp%3A8.0-fpm-apache)](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm-apache&page=1&ordering=-name)<br />[![serversideup/php:7.4-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm-apache?label=serversideup%2Fphp%3A7.4-fpm-apache)](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm-apache&page=1&ordering=-name) |
| fpm-nginx | [![serversideup/php:8.2-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm-nginx?label=serversideup%2Fphp%3A8.2-fpm-nginx)](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm-nginx&page=1&ordering=-name)<br />[![serversideup/php:8.1-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm-nginx?label=serversideup%2Fphp%3A8.1-fpm-nginx)](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm-nginx&page=1&ordering=-name)<br />[![serversideup/php:8.0-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm-nginx?label=serversideup%2Fphp%3A8.0-fpm-nginx)](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm-nginx&page=1&ordering=-name)<br />[![serversideup/php:7.4-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm-nginx?label=serversideup%2Fphp%3A7.4-fpm-nginx)](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm-nginx&page=1&ordering=-name) |
| cli | <span class="not-prose mb-1 block">[![serversideup/php:8.3-cli](https://img.shields.io/docker/image-size/serversideup/php/8.3-cli?label=serversideup%2Fphp%3A8.3-cli)](https://hub.docker.com/r/serversideup/php/tags?name=8.3-cli&page=1&ordering=-name)</span><span class="not-prose mb-1 block">[![serversideup/php:8.2-cli](https://img.shields.io/docker/image-size/serversideup/php/8.2-cli?label=serversideup%2Fphp%3A8.2-cli)](https://hub.docker.com/r/serversideup/php/tags?name=8.2-cli&page=1&ordering=-name)</span><span class="not-prose mb-1 block">[![serversideup/php:8.1-cli](https://img.shields.io/docker/image-size/serversideup/php/8.1-cli?label=serversideup%2Fphp%3A8.1-cli)](https://hub.docker.com/r/serversideup/php/tags?name=8.1-cli&page=1&ordering=-name)</span><span class="not-prose mb-1 block">[![serversideup/php:8.0-cli](https://img.shields.io/docker/image-size/serversideup/php/8.0-cli?label=serversideup%2Fphp%3A8.0-cli)](https://hub.docker.com/r/serversideup/php/tags?name=8.0-cli&page=1&ordering=-name)</span><span class="not-prose mb-1 block">[![serversideup/php:7.4-cli](https://img.shields.io/docker/image-size/serversideup/php/7.4-cli?label=serversideup%2Fphp%3A7.4-cli)](https://hub.docker.com/r/serversideup/php/tags?name=7.4-cli&page=1&ordering=-name)</span> |
| fpm | <span class="not-prose mb-1 block">[![serversideup/php:8.3-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.3-fpm?label=serversideup%2Fphp%3A8.3-fpm)](https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm&page=1&ordering=-name)</span><span class="not-prose mb-1 block">[![serversideup/php:8.2-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm?label=serversideup%2Fphp%3A8.2-fpm)](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm&page=1&ordering=-name)</span><span class="not-prose mb-1 block">[![serversideup/php:8.1-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm?label=serversideup%2Fphp%3A8.1-fpm)](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm&page=1&ordering=-name)</span><span class="not-prose mb-1 block">[![serversideup/php:8.0-fpm](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm?label=serversideup%2Fphp%3A8.0-fpm)](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm&page=1&ordering=-name)</span><span class="not-prose mb-1 block">[![serversideup/php:7.4-fpm](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm?label=serversideup%2Fphp%3A7.4-fpm)](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm&page=1&ordering=-name)</span> |
| fpm-apache | <span class="not-prose mb-1 block">[![serversideup/php:8.3-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.3-fpm-apache?label=serversideup%2Fphp%3A8.3-fpm-apache)](https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm-apache&page=1&ordering=-name)</span><span class="not-prose mb-1 block">[![serversideup/php:8.2-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm-apache?label=serversideup%2Fphp%3A8.2-fpm-apache)](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm-apache&page=1&ordering=-name)</span><span class="not-prose mb-1 block">[![serversideup/php:8.1-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm-apache?label=serversideup%2Fphp%3A8.1-fpm-apache)](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm-apache&page=1&ordering=-name)</span><span class="not-prose mb-1 block">[![serversideup/php:8.0-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm-apache?label=serversideup%2Fphp%3A8.0-fpm-apache)](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm-apache&page=1&ordering=-name)</span><span class="not-prose mb-1 block">[![serversideup/php:7.4-fpm-apache](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm-apache?label=serversideup%2Fphp%3A7.4-fpm-apache)](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm-apache&page=1&ordering=-name)</span> |
| fpm-nginx | <span class="not-prose mb-1 block">[![serversideup/php:8.3-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.3-fpm-nginx?label=serversideup%2Fphp%3A8.3-fpm-nginx)](https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm-nginx&page=1&ordering=-name)</span><span class="not-prose mb-1 block">[![serversideup/php:8.2-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.2-fpm-nginx?label=serversideup%2Fphp%3A8.2-fpm-nginx)](https://hub.docker.com/r/serversideup/php/tags?name=8.2-fpm-nginx&page=1&ordering=-name)</span><span class="not-prose mb-1 block">[![serversideup/php:8.1-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.1-fpm-nginx?label=serversideup%2Fphp%3A8.1-fpm-nginx)](https://hub.docker.com/r/serversideup/php/tags?name=8.1-fpm-nginx&page=1&ordering=-name)</span><span class="not-prose mb-1 block">[![serversideup/php:8.0-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/8.0-fpm-nginx?label=serversideup%2Fphp%3A8.0-fpm-nginx)](https://hub.docker.com/r/serversideup/php/tags?name=8.0-fpm-nginx&page=1&ordering=-name)</span><span class="not-prose mb-1 block">[![serversideup/php:7.4-fpm-nginx](https://img.shields.io/docker/image-size/serversideup/php/7.4-fpm-nginx?label=serversideup%2Fphp%3A7.4-fpm-nginx)](https://hub.docker.com/r/serversideup/php/tags?name=7.4-fpm-nginx&page=1&ordering=-name)</span> |
| unit | <span class="not-prose mb-1 block">[![serversideup/php:8.3-unit](https://img.shields.io/docker/image-size/serversideup/php/8.3-unit?label=serversideup%2Fphp%3A8.3-unit)](https://hub.docker.com/r/serversideup/php/tags?name=8.3-unit&page=1&ordering=-name)</span><span class="not-prose mb-1 block">[![serversideup/php:8.2-unit](https://img.shields.io/docker/image-size/serversideup/php/8.2-unit?label=serversideup%2Fphp%3A8.2-unit)](https://hub.docker.com/r/serversideup/php/tags?name=8.2-unit&page=1&ordering=-name)</span><span class="not-prose mb-1 block">[![serversideup/php:8.1-unit](https://img.shields.io/docker/image-size/serversideup/php/8.1-unit?label=serversideup%2Fphp%3A8.1-unit)](https://hub.docker.com/r/serversideup/php/tags?name=8.1-unit&page=1&ordering=-name)</span><span class="not-prose mb-1 block">[![serversideup/php:8.0-unit](https://img.shields.io/docker/image-size/serversideup/php/8.0-unit?label=serversideup%2Fphp%3A8.0-unit)](https://hub.docker.com/r/serversideup/php/tags?name=8.0-unit&page=1&ordering=-name)</span><span class="not-prose mb-1 block">[![serversideup/php:7.4-unit](https://img.shields.io/docker/image-size/serversideup/php/7.4-unit?label=serversideup%2Fphp%3A7.4-unit)](https://hub.docker.com/r/serversideup/php/tags?name=7.4-unit&page=1&ordering=-name)</span> |

### Real-life working example
You can see a bigger picture on how these images are used from Development to Production by viewing this video that shows a high level overview how we deploy "[ROAST](https://roastandbrew.coffee/)" which is a demo production app for [our book](https://serversideup.net/ultimate-guide-to-building-apis-and-spas-with-laravel-and-vuejs/).
Expand Down
5 changes: 1 addition & 4 deletions docs/.env.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
NUXT_APP_BASE_URL=/open-source/docker-php
TOP_LEVEL_DOMAIN=http://localhost:3000
BASE_PATH=http://localhost:3000/open-source/docker-php
TOP_LEVEL_DOMAIN=http://localhost:3000
ALGOLIA_API_KEY=changeme
ALGOLIA_APPLICATION_ID=changeme
ALGOLIA_INDEX=changeme
TOP_LEVEL_DOMAIN=http://localhost:3000
11 changes: 4 additions & 7 deletions docs/components/DocumentDrivenNotFound.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,16 @@ switch( redirectPath ){
navigateTo( redirectPath+'/these-images-vs-others', { replace: true } );
break;
case '/docs/guide':
navigateTo( redirectPath+'/choosing-the-right-image', { replace: true } );
navigateTo( redirectPath+'/migrating-from-official-php-images', { replace: true } );
break;
case '/docs/reference':
navigateTo( redirectPath+'/environment-variable-specification', { replace: true } );
break;
case '/docs/laravel':
navigateTo( redirectPath+'/laravel-autorun-script', { replace: true } );
navigateTo( redirectPath+'/laravel-automations', { replace: true } );
break;
case '/docs/wordpress':
navigateTo( redirectPath+'/wordpress-optimizations', { replace: true } );
break;
case '/docs/reference':
navigateTo( redirectPath+'/environment-variable-specification', { replace: true } );
case '/docs/customizing-the-image':
navigateTo( redirectPath+'/changing-common-php-settings', { replace: true } );
break;
}
</script>
4 changes: 0 additions & 4 deletions docs/content/docs/1.index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ head.title: 'Introduction - Docker Images - Server Side Up'
layout: docs
---

::note
These docs refer to the latest [**v3 Beta**](https://github.com/serversideup/docker-php/discussions/254), which are expected to hit "stable" soon. If you are looking for the v2 docs, you can find them [here](https://github.com/serversideup/docker-php/tree/v2.x/docs/content/docs).
::

# Introduction

<video loop autoplay muted playsinline class="w-full" src="https://docker-php-public-assets.serversideup.net/docker-demo.mp4"></video>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ layout: docs
| Multi-arch support |||
| Init System | Docker CMD | Docker CMD or [S6-Overlay](https://github.com/just-containers/s6-overlay) |
| Published Registry| DockerHub | [DockerHub](https://hub.docker.com/r/serversideup/php), [GitHub Packages](https://github.com/serversideup/docker-php/pkgs/container/php) |
| Unprivileged by default |||
| Variable-first configuration |||
| Includes `composer` |||
| Includes [`install-php-extensions`](https://github.com/mlocati/docker-php-extension-installer) |||
Expand All @@ -24,6 +25,11 @@ layout: docs
| NGINX Unit variation|||
| Native health checks |||

## Unprivileged by Default
We believe in the principle of least privilege. Our images run as an unprivileged user by default. This means that if your application is compromised, the attacker will have a harder time escalating their privileges to the root user.

Running unprivileged images also improves compatibility of running your containers in a Kubernetes environment, where running as root is not allowed.

## Variable-first Configuration
Our design philosophy is built all around simplicity. The process of customizing the behavior of PHP is as simple as setting an environment variable. We took every common configuration option and set it up so you can change these values in a simple method, defaulting every single option to production-ready values.

Expand Down
2 changes: 1 addition & 1 deletion docs/content/docs/2.getting-started/2.installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ The `fpm` variation is great for people who need to run a PHP "backend" if they
### FPM-Apache
The `fpm-apache` variation is meant for users who want to run something like WordPress with Docker. Apache is configured to be a "reverse proxy", which will serve any static content with Apache and serve any PHP requests with PHP-FPM. Since there are two processes required to run this variation, we use [S6 Overlay](/docs/guide/using-s6-overlay) to ensure the container health is accurate.

[Learn more about using Docker with WordPress →](/docs/wordpress/wordpress-optimizations)
[Learn more about using Docker with WordPress →](/docs/guide/using-wordpress-with-docker)

### FPM-NGINX
The `fpm-nginx` variation is great for people who want to run Laravel applications or similar. This allows you to serve static content quickly with NGINX but also pass PHP requests to PHP-FPM. Similar to PHP-Apache, there are two proccess required to run this variation. We use S6 Overlay to ensure the container health is accurate.
Expand Down
36 changes: 36 additions & 0 deletions docs/content/docs/2.getting-started/3.default-configurations.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,45 @@ layout: docs
## Production-ready and optimized for Laravel & WordPress
All values are defaulted to improve security and performance. We also spent the time to carefully review official documentation and include packages that are required specifically for Laravel and WordPress.

## Unprivileged by Default
All images default to running as the OS-native `www-data` user.

::note
The `www-data` UID/GID is different between Debian (`33:33`) and Alpine (`82:82`). We left these values alone to make these images as native as possible. If you switch between Debian and Alpine, you may need to adjust file permissions in your Docker image and volume mounts.
::

Since these images are not privileged, that means they are not running on ports less than 1024:

| **Variation** | **Default Ports** |
|---------------|-------------------|
| cli | (none) |
| fpm | 9000 |
| fpm-nginx | HTTP: 8080, HTTPS: 8443 |
| fpm-apache | HTTP: 8080, HTTPS: 8443 |
| unit | HTTP: 8080, HTTPS: 8443 |

### How do I run these services on ports 80 and/or 443?
Almost everyone will want to run these services on ports 80 and 443. If you have an advanced setup, you can use a reverse proxy like Caddy or Traefik to handle the SSL termination and forward the traffic to the container on the non-privileged port.

Or you can simply use Docker's port mapping feature to map the container port to the host port. For example, to run the `fpm-nginx` variation on port 80 and 443, you can run the following command:

::code-panel
---
label: Run FPM NGINX on port 80 and 443
---
```bash
docker run -p 80:8080 -p 443:8443 serversideup/php:8.3-fpm-nginx
```
::

## Default Environment Variables
We allow the ability to customize the behavior of PHP with environment variables. Be sure to review our production-ready default values on our [environment variable specification](/docs/reference/environment-variable-specification) page.

## Default PHP INI Settings
We provide a default PHP ini that come with the suggested and hardened settings for running PHP in production. This file is located at `/usr/local/etc/php/conf.d/serversideup-docker-php.ini`.

To customize the PHP ini settings, read our [Changing Common PHP Settings](/docs/customizing-the-image/changing-common-php-settings) guide.

## Default PHP Extensions
The following extensions are installed by default:

Expand Down
18 changes: 16 additions & 2 deletions docs/content/docs/2.getting-started/3.upgrade-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ Any updates that you apply have a risk of breaking other things inside the conta

::code-panel
---
label: Example Dockerfile with manual updates
label: Example Dockerfile with manual updates for Debian
---
```txt
```dockerfile
FROM serversideup/php:8.3.2-fpm-nginx

RUN apt-get update \
Expand All @@ -38,6 +38,20 @@ RUN apt-get update \
```
::

If you're running an Alpine-based image, you can use the following commands:

::code-panel
---
label: Example Dockerfile with manual updates for Alpine
---
```dockerfile
FROM serversideup/php:8.3.2-fpm-nginx-alpine

RUN apk update \
&& apk upgrade \
&& rm -rf /var/cache/apk/*
```
::

## Subscribe to repository updates
Regardless if you are choosing to use automatic updates or manual updates, it is highly advised to subscribe to our releases. You can do this through the "Watch" button on our [GitHub](https://github.com/serversideup/docker-php).
Expand Down
5 changes: 4 additions & 1 deletion docs/content/docs/2.getting-started/4.choosing-a-host.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ We believe privacy and control is the #1 priority when it comes to hosting infra

We run all our production servers on the latest LTS release of Ubuntu Server. The hosts we use are below. Some may be affiliate links that kick a few bucks at no extra cost to you, but they do not affect our recommendations at all.

### [Hetzner](https://hetzner.cloud/?ref=lhLUIrkdUPhl)
**Our current favorite.** Your mind will be blown for the specs you get for the prices. They are based in Europe, but have US datacenters too. We're running full out SaaS products for $5 USD a month. 🤯

### [Vultr](https://vultr.grsm.io/create)
**Our current favorite.** Excellent performance and value. Lots of datacenter options too.
Excellent performance and value. Lots of data center options too.

### [Digital Ocean](https://m.do.co/c/f3bad4b927ca)
Lots of developer love here. Not the best performing servers, but they do have a lot of awesome products!
Expand Down
Loading

0 comments on commit bcc5b18

Please sign in to comment.