Analytics cookies only (Google Analytics, PostHog). No ad networks, no cross-site tracking. See our privacy policy.
Ea
Self-host Easy!Appointments
updated Oct 2026deployed by usprices checked · Jul 2026Install verified on Ubuntu 26.04 · Oct 2026 · how we test
We earn commissions when you shop through the links below. Full disclosure →
An appointment scheduler for a business with staff: a public booking page where customers pick a service, a provider and a slot, with a back office for working plans, services, providers, secretaries, customers, email notifications and Google Calendar or CalDAV sync. GPL-3.0 PHP (CodeIgniter) on Apache with MySQL; the official image configures itself from environment variables and runs the web installer on first visit.
Runs on a VPS from $6/mo (Kamatera 2GB, 2 GB RAM) · prices checked Sep 2026
Our recommendation
Pick Easy!Appointments when customers need to book a slot with a specific person: it models services, providers with their own working plans and booking rules, secretaries who manage bookings for others, and customers, with a public booking page, email notifications, a translated interface, a REST API and Google Calendar or CalDAV sync. It is GPL-3.0 PHP (CodeIgniter) on Apache with MySQL, and the official image writes `config.php` and the mail configuration from environment variables on every start. Our snippet is the compose example from the image's README with the image pinned, a dedicated database user instead of root, and generated passwords; the installer in the browser creates the admin account. The installation guide lists Apache, PHP and MySQL versions but no RAM figure, so our 1 GB floor is an estimate that mostly covers MySQL. In the catalog, Cal-DIY is the Cal.com community edition for personal booking links and Rallly is a date poll; Easy!Appointments is the one built for a shop, clinic or salon with staff.
What you need
Any VPS with at least 1024 MB of RAM
A domain you control — most self-hosted setups need HTTPS in front of them
Paid link — we earn a commission if you shop through it.
Install
Run these commands on your server:
# Easy!Appointments — appointment scheduler with a public booking page — based on the docker-compose.yml in the official image's README (github.com/alextselegidis/easyappointments-docker), pinned to 1.6.0echo "DB_PASSWORD=$(openssl rand -hex 16)" > .envecho "DB_ROOT_PASSWORD=$(openssl rand -hex 16)" >> .envcat > docker-compose.yml <<'EOF'services: easyappointments: image: alextselegidis/easyappointments:1.6.0 restart: unless-stopped ports: - 80:80 environment: BASE_URL: http://SERVER_IP DB_HOST: mysql DB_NAME: easyappointments DB_USERNAME: easyappointments DB_PASSWORD: ${DB_PASSWORD} depends_on: - mysql mysql: image: mysql:8.4.11 restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD} MYSQL_DATABASE: easyappointments MYSQL_USER: easyappointments MYSQL_PASSWORD: ${DB_PASSWORD} volumes: - mysql_data:/var/lib/mysqlvolumes: mysql_data:EOFdocker compose up -dtimeout 300 bash -c 'until curl -fsS http://localhost/ >/dev/null; do sleep 5; done'# → http://SERVER_IP:80 — the first visit opens the installer where you create the admin account; BASE_URL must be the public URL people use (replace SERVER_IP above)
The easyappointments-docker README, the image entrypoint and the installation guide flag these before you publish the booking page:
non-negotiableThere are no default credentials: the first visit opens the install wizard where you enter the admin account and company details. The installation guide says the app then comes with test data, so set working hours under Settings, then add real services and providers and remove the samples. The image's built-in defaults are root / secret for the database; our snippet replaces them with a dedicated easyappointments user and generated passwords in .env.
non-negotiableOnly the MySQL volume (mysql_data) persists in upstream's compose; the application directory, including the storage folder the installation guide describes as holding logs and temporary files, lives inside the container. Upgrading is editing the image tag and docker compose up -d, then following the project's update guide for the database step; back up the database first.
non-negotiableUpstream's example runs alextselegidis/easyappointments:latest with an older MySQL line and DEBUG_MODE=TRUE; we pin the app image, use the current MySQL LTS line, and leave DEBUG_MODE at the image's FALSE default, which is the right setting for anything public. MAIL_PROTOCOL defaults to mail (PHP's local sendmail, which the container does not have), so set the MAIL_SMTP_* and MAIL_FROM_* variables from the README before relying on notifications.
non-negotiableApache serves plain HTTP on port 80, mapped to 80 here; put a TLS reverse proxy in front. BASE_URL is written into config.php and appended to the framework config as base_url, and the app builds every link from it, so replace the http://SERVER_IP placeholder with the public address before customers see the page; a wrong value shows up as broken assets and redirects. Google Calendar sync additionally needs GOOGLE_SYNC_FEATURE, the client ID and secret, and the php_curl extension the guide mentions.