Since getting a Magic Keyboard for my iPad Pro, I’ve been using the iPad for many areas of work for which before I would have needed a laptop.
In fact, last week I was able to use the iPad full-time when at work in our new office space, and I didn’t need to reach for my MacBook once. When I can, I prefer working on the iPad due to its flexibility, brilliant display, speed, battery life, and more.
I also prefer working in the single-context view iPadOS provides. On a Mac I would have dozens of windows open at any one time, offering lots of distraction. On iPad, I generally use one screen at a time, allowing me to keep focus. In in-person meetings, the iPad’s form-factor also feels less of a psychological barrier between myself and my colleagues. In video meetings, the iPad also holds up well, and its screen-sharing functionality is fantastic.
My role has changed in recent years and, as a result, I do less coding in my day-to-day work at my job. However, this is one area where native iPad working does fall down, since the system does not offer native command line tooling or the ability to install and run development software. During the past week there were a couple of times where it would’ve been useful for me to contribute some code with my team and - not wanting to have to return to the MacBook just for these moments - I spent a little time setting-up a development environment on my iPad.
In this post I will talk a little about my approach to setting-up a development workflow from my iPad, using a VPS, Tailscale, Termius, and Code Server.
✌️ As a bonus, all of the steps in this post can also be completed on an iPad. This post was also written, and the site was built and deployed, from my iPad.
1. Provision a VPS
Since the iPad itself does not have the tooling needed to write, run, and build applications, I provisioned a Linux cloud server that would do most of the heavy lifting.
I spun up a new Ubuntu 22.04 server on Linode with sufficient resources needed to run and build my apps. I went for 4GB memory and 2 vCPUs, but Linode lets you change this later if you realise you need less or more resources. My intention is to use this machine solely for development work.
2. Configure Docker and initial server set-up
The next steps were to access the new server and set-up the environment.
Termius is an excellent app for managing remote SSH and file-transfer sessions, and it’s even better on iPad with Magic Keyboard. It’s free to use but their premium subscription is worth it too.
Using Termius, I connected to the new server (using my SSH key) and firstly installed Docker and Docker Compose (which I will use to run the Code Server instance):
# apt update # apt install docker docker-compose # service docker start
Next, I created a user that I would use for management and for running the Docker containers:
# useradd will # ... go through setup # usermod -aG docker will
I recommend editing
/etc/ssh/sshd_config to harden your SSH setup, restrict root login and only permit key-based authentication. Don’t forget to restart the SSHD service after changing the settings. You should also add any needed SSH keys to your non-root user’s home directory so that they can login later.
3. Configure Tailscale and firewalls
Tailscale is a fantastic service that allows you to setup a VPN mesh network for all of your devices, based on the Wireguard protocol. By leveraging Tailscale, we can ensure that access to the server is restricted only to devices you’ve added to your Tailnet.
First, create an account with Tailscale if you haven’t got one already (it’s free for up to 20 devices!) and then install Tailscale on the server:
# curl -fsSL https://tailscale.com/install.sh | sh # tailscale up
Follow the instructions to authorise the machine with your Tailscale account, and then you’ll be able to see the server listed on your Tailscale’s account webpage.
Next, ensure you have the Tailscale app installed and connected for your iPad. Your iPad should then also become listed on the Tailscale website.
Finally, configure your firewalls to block all public access. If you use Linode, you can make use of their cloud firewall service to drop all inbound traffic.
Finally, verify that you can still connect to the development server by modifying your VPS IP address in Termius to the Tailscale one shown on the website (or the DNS name if you’ve enabled MagicDNS!).
You can now be sure that only your authorised Tailscale devices can talk to your server.
4. Run Code Server
Use Termius to log back into your development VPS with your non-root user. We’ll now set-up Code Server on the server.
Code Server is a web-based implementation of Microsoft’s VSCode. linuxserver.io maintain a community Docker image, which is what I use.
docker-compose.yml file on your server with these contents:
version: "2.1" services: code-server: image: lscr.io/linuxserver/code-server:latest container_name: code-server environment: - PUID=1000 - PGID=1000 - TZ=Europe/London - PASSWORD=password - SUDO_PASSWORD=password - PROXY_DOMAIN=development - DEFAULT_WORKSPACE=/config/workspace volumes: - ./code:/config ports: - 8443:8443 restart: unless-stopped
Change the values for the password (used to access the Code web interface) and the
sudo password (which you can use to add extra packages to the container later).
When happy, bring up the Code Server with
docker-compose up -d. After a few seconds, you should be able to visit your Code Server instance in Safari by navigating to the Tailscale IP address (or MagicDNS entry) on port
You can now use Code in much the same way as you would with the desktop app.
5. Post-setup tasks
Whilst you can now write code via Safari on your iPad, extra setup will be needed to actually do anything with the code.
When managing the Code Server container, I recommend simply opening a Terminal window from within the Code Server web interface itself. You can use the
SUDO_PASSWORD you configured earlier for tasks that require root access.
For example, to set up remote git properly, you’ll want to create a new SSH keypair and add the public key to your git host.
If you need extra software - for example, Node/yarn for JS apps, Python, Ruby, or anything else - then install these via
apt. Such changes should persist properly between container restarts, meaning that you can build out your development environment in the way that suits you best.
If you want to be able to run your apps - for example a Python Flask app on port 5000 - and access via a browser, then just add a mapping for the port to your
Optional next steps
Although Tailscale is encrypting your traffic, you may want to provision a TLS certficate and use a reverse-proxy, like Traefik, to serve Code Server.
To do so, I recommend following the instructions on the Tailscale website.
Whilst the approach in this post will provide a working development environment for writing, building, and deploying many types of software, it isn’t suitable for all apps.
For example, mobile development (in which a connected device or simulator might be required) will be tricky. Similarly, coding for Apple devices (macOS or iOS) requires XCode, which is currently only available on MacOS.
Even web development can be a little fiddly without direct access to developer tools in a browser. However, for light usage, I really enjoy this approach and I am excited about the possibilities as the ecosystem continues to develop.